Prepare modules
This commit is contained in:
parent
099267ee07
commit
7d8566350d
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@
|
||||
/.phpunit.result.cache
|
||||
/.zed
|
||||
/.idea
|
||||
composer.lock
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
use Koneko\VuexyAdmin\Services\VuexyAdminService;
|
||||
|
||||
class AdminController extends Controller
|
||||
{
|
||||
public function searchNavbar()
|
||||
{
|
||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
||||
|
||||
$VuexyAdminService = app(VuexyAdminService::class);
|
||||
|
||||
return response()->json($VuexyAdminService->getVuexySearchData());
|
||||
}
|
||||
|
||||
public function quickLinksUpdate(Request $request)
|
||||
{
|
||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
||||
|
||||
$validated = $request->validate([
|
||||
'action' => 'required|in:update,remove',
|
||||
'route' => 'required|string',
|
||||
]);
|
||||
|
||||
$quickLinks = Setting::where('user_id', Auth::user()->id)
|
||||
->where('key', 'quicklinks')
|
||||
->first();
|
||||
|
||||
$quickLinks = $quickLinks ? json_decode($quickLinks->value, true) : [];
|
||||
|
||||
if ($validated['action'] === 'update') {
|
||||
// Verificar si ya existe
|
||||
if (!in_array($validated['route'], $quickLinks))
|
||||
$quickLinks[] = $validated['route'];
|
||||
} elseif ($validated['action'] === 'remove') {
|
||||
// Eliminar la ruta si existe
|
||||
$quickLinks = array_filter($quickLinks, function ($route) use ($validated) {
|
||||
return $route !== $validated['route'];
|
||||
});
|
||||
}
|
||||
|
||||
Setting::updateOrCreate(['user_id' => Auth::user()->id, 'key' => 'quicklinks'], ['value' => json_encode($quickLinks)]);
|
||||
|
||||
VuexyAdminService::clearQuickLinksCache();
|
||||
}
|
||||
|
||||
public function generalSettings()
|
||||
{
|
||||
return view('vuexy-admin::admin-settings.webapp-general-settings');
|
||||
}
|
||||
|
||||
public function smtpSettings()
|
||||
{
|
||||
return view('vuexy-admin::admin-settings.smtp-settings');
|
||||
}
|
||||
}
|
@ -8,6 +8,13 @@ use Koneko\VuexyAdmin\Services\CacheConfigService;
|
||||
|
||||
class CacheController extends Controller
|
||||
{
|
||||
public function index(CacheConfigService $cacheConfigService)
|
||||
{
|
||||
$configCache = $cacheConfigService->getConfig();
|
||||
|
||||
return view('vuexy-admin::cache-manager.index', compact('configCache'));
|
||||
}
|
||||
|
||||
public function generateConfigCache()
|
||||
{
|
||||
try {
|
||||
@ -27,15 +34,9 @@ class CacheController extends Controller
|
||||
Artisan::call('route:cache');
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Cache de rutas generado correctamente.']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['success' => false, 'message' => 'Error al generar el cache de rutas.', 'error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function cacheManager(CacheConfigService $cacheConfigService)
|
||||
{
|
||||
$configCache = $cacheConfigService->getConfig();
|
||||
|
||||
return view('vuexy-admin::cache-manager.index', compact('configCache'));
|
||||
}
|
||||
}
|
||||
|
75
Http/Controllers/GlobalSettingsController.php
Normal file
75
Http/Controllers/GlobalSettingsController.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Koneko\VuexyAdmin\Queries\GenericQueryBuilder;
|
||||
|
||||
class GlobalSettingsController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->ajax()) {
|
||||
$bootstrapTableIndexConfig = [
|
||||
'table' => 'settings',
|
||||
'columns' => [
|
||||
'settings.id',
|
||||
'settings.key',
|
||||
'settings.category',
|
||||
'settings.user_id',
|
||||
DB::raw("CONCAT_WS(' ', users.name, users.last_name) AS user_fullname"),
|
||||
'settings.value_string',
|
||||
'settings.value_integer',
|
||||
'settings.value_boolean',
|
||||
'settings.value_float',
|
||||
DB::raw("IF(LENGTH(settings.value_text) > 60, CONCAT(LEFT(settings.value_text, 60), '..'), settings.value_text) AS value_text"),
|
||||
DB::raw("IF(settings.value_binary, '-BINARY-', '') AS value_binary"),
|
||||
'settings.mime_type',
|
||||
'settings.file_name',
|
||||
'settings.created_at',
|
||||
'settings.updated_at',
|
||||
'settings.updated_by',
|
||||
DB::raw("CONCAT_WS(' ', creator.name, creator.last_name) AS creator_name"),
|
||||
],
|
||||
'joins' => [
|
||||
[
|
||||
'table' => 'users',
|
||||
'first' => 'settings.user_id',
|
||||
'second' => 'users.id',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'users',
|
||||
'first' => 'settings.updated_by',
|
||||
'second' => 'creator.id',
|
||||
'type' => 'leftJoin',
|
||||
'alias' => 'creator',
|
||||
],
|
||||
],
|
||||
'filters' => [
|
||||
'search' => [
|
||||
'settings.key',
|
||||
'settings.category',
|
||||
'users.name',
|
||||
'users.last_name',
|
||||
'creator.name',
|
||||
'creator.last_name',
|
||||
],
|
||||
],
|
||||
'sort_column' => 'settings.key',
|
||||
'default_sort_order' => 'asc',
|
||||
];
|
||||
|
||||
return (new GenericQueryBuilder($request, $bootstrapTableIndexConfig))->getJson();
|
||||
}
|
||||
|
||||
return view('vuexy-admin::global-settings.index');
|
||||
}
|
||||
}
|
@ -3,11 +3,8 @@
|
||||
namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Yajra\DataTables\Facades\DataTables;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Koneko\VuexyAdmin\Queries\GenericQueryBuilder;
|
||||
|
||||
class PermissionController extends Controller
|
||||
{
|
||||
@ -19,17 +16,26 @@ class PermissionController extends Controller
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->ajax()) {
|
||||
$permissions = Permission::latest()->get();
|
||||
$bootstrapTableIndexConfig = [
|
||||
'table' => 'permissions',
|
||||
'columns' => [
|
||||
'permissions.id',
|
||||
'permissions.name',
|
||||
'permissions.group_name',
|
||||
'permissions.sub_group_name',
|
||||
'permissions.action',
|
||||
'permissions.guard_name',
|
||||
'permissions.created_at',
|
||||
'permissions.updated_at',
|
||||
],
|
||||
'filters' => [
|
||||
'search' => ['permissions.name', 'permissions.group_name', 'permissions.sub_group_name', 'permissions.action'],
|
||||
],
|
||||
'sort_column' => 'permissions.name',
|
||||
'default_sort_order' => 'asc',
|
||||
];
|
||||
|
||||
return DataTables::of($permissions)
|
||||
->addIndexColumn()
|
||||
->addColumn('assigned_to', function ($row) {
|
||||
return (Arr::pluck($row->roles, ['name']));
|
||||
})
|
||||
->editColumn('created_at', function ($request) {
|
||||
return $request->created_at->format('Y-m-d h:i:s a');
|
||||
})
|
||||
->make(true);
|
||||
return (new GenericQueryBuilder($request, $bootstrapTableIndexConfig))->getJson();
|
||||
}
|
||||
|
||||
return view('vuexy-admin::permissions.index');
|
||||
|
@ -1,188 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Yajra\DataTables\Facades\DataTables;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyAdmin\Services\AvatarImageService;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->ajax()) {
|
||||
$users = User::when(!Auth::user()->hasRole('SuperAdmin'), function ($query) {
|
||||
$query->where('id', '>', 1);
|
||||
})
|
||||
->latest()
|
||||
->get();
|
||||
|
||||
return DataTables::of($users)
|
||||
->only(['id', 'name', 'email', 'avatar', 'roles', 'status', 'created_at'])
|
||||
->addIndexColumn()
|
||||
->addColumn('avatar', function ($user) {
|
||||
return $user->profile_photo_url;
|
||||
})
|
||||
->addColumn('roles', function ($user) {
|
||||
return (Arr::pluck($user->roles, ['name']));
|
||||
})
|
||||
/*
|
||||
->addColumn('stores', function ($user) {
|
||||
return (Arr::pluck($user->stores, ['nombre']));
|
||||
})
|
||||
y*/
|
||||
->editColumn('created_at', function ($user) {
|
||||
return $user->created_at->format('Y-m-d');
|
||||
})
|
||||
->make(true);
|
||||
}
|
||||
|
||||
return view('vuexy-admin::users.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|max:255',
|
||||
'email' => 'required|max:255|unique:users',
|
||||
'photo' => 'nullable|mimes:jpg,jpeg,png|max:1024',
|
||||
'password' => 'required',
|
||||
]);
|
||||
|
||||
if ($validator->fails())
|
||||
return response()->json(['errors' => $validator->errors()->all()]);
|
||||
|
||||
// Preparamos los datos
|
||||
$user_request = array_merge_recursive($request->all(), [
|
||||
'remember_token' => Str::random(10),
|
||||
'created_by' => Auth::user()->id,
|
||||
]);
|
||||
|
||||
$user_request['password'] = bcrypt($request->password);
|
||||
|
||||
// Guardamos el nuevo usuario
|
||||
$user = User::create($user_request);
|
||||
|
||||
// Asignmos los permisos
|
||||
$user->assignRole($request->roles);
|
||||
|
||||
// Asignamos Sucursals
|
||||
//$user->stores()->attach($request->stores);
|
||||
|
||||
if ($request->file('photo')){
|
||||
$avatarImageService = new AvatarImageService();
|
||||
|
||||
$avatarImageService->updateProfilePhoto($user, $request->file('photo'));
|
||||
}
|
||||
|
||||
return response()->json(['success' => 'Se agrego correctamente el usuario']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int User $user
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function show(User $user)
|
||||
{
|
||||
return view('vuexy-admin::users.show', compact('user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param int User $user
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function updateAjax(Request $request, User $user)
|
||||
{
|
||||
// Validamos los datos
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => 'required|max:191',
|
||||
'email' => "required|max:191|unique:users,email," . $user->id,
|
||||
'photo' => 'nullable|mimes:jpg,jpeg,png|max:2048'
|
||||
]);
|
||||
|
||||
if ($validator->fails())
|
||||
return response()->json(['errors' => $validator->errors()->all()]);
|
||||
|
||||
// Preparamos los datos
|
||||
$user_request = $request->all();
|
||||
|
||||
if ($request->password) {
|
||||
$user_request['password'] = bcrypt($request->password);
|
||||
} else {
|
||||
unset($user_request['password']);
|
||||
}
|
||||
|
||||
// Guardamos los cambios
|
||||
$user->update($user_request);
|
||||
|
||||
// Sincronizamos Roles
|
||||
$user->syncRoles($request->roles);
|
||||
|
||||
// Sincronizamos Sucursals
|
||||
//$user->stores()->sync($request->stores);
|
||||
|
||||
// Actualizamos foto de perfil
|
||||
if ($request->file('photo'))
|
||||
$avatarImageService = new AvatarImageService();
|
||||
|
||||
$avatarImageService->updateProfilePhoto($user, $request->file('photo'));
|
||||
|
||||
return response()->json(['success' => 'Se guardo correctamente los cambios.']);
|
||||
}
|
||||
|
||||
|
||||
public function userSettings(User $user)
|
||||
{
|
||||
return view('vuexy-admin::users.user-settings', compact('user'));
|
||||
}
|
||||
|
||||
public function generateAvatar(Request $request)
|
||||
{
|
||||
// Validación de entrada
|
||||
$request->validate([
|
||||
'name' => 'nullable|string',
|
||||
'color' => 'nullable|string|size:6',
|
||||
'background' => 'nullable|string|size:6',
|
||||
'size' => 'nullable|integer|min:20|max:1024'
|
||||
]);
|
||||
|
||||
$name = $request->get('name', 'NA');
|
||||
$color = $request->get('color', '7F9CF5');
|
||||
$background = $request->get('background', 'EBF4FF');
|
||||
$size = $request->get('size', 100);
|
||||
|
||||
return User::getAvatarImage($name, $color, $background, $size);
|
||||
|
||||
try {
|
||||
} catch (\Exception $e) {
|
||||
// String base64 de una imagen PNG transparente de 1x1 píxel
|
||||
$transparentBase64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==';
|
||||
|
||||
return response()->make(base64_decode($transparentBase64), 200, [
|
||||
'Content-Type' => 'image/png'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\{Auth,DB,Validator};
|
||||
use Illuminate\Support\Facades\{Auth,Validator};
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyAdmin\Services\AvatarImageService;
|
||||
use Koneko\VuexyAdmin\Queries\GenericQueryBuilder;
|
||||
@ -24,97 +24,19 @@ class UserController extends Controller
|
||||
'table' => 'users',
|
||||
'columns' => [
|
||||
'users.id',
|
||||
'users.code',
|
||||
DB::raw("CONCAT_WS(' ', users.name, users.last_name) AS full_name"),
|
||||
'users.name AS full_name',
|
||||
'users.email',
|
||||
'users.birth_date',
|
||||
'users.hire_date',
|
||||
'users.curp',
|
||||
'users.nss',
|
||||
'users.job_title',
|
||||
'users.email_verified_at',
|
||||
'users.profile_photo_path',
|
||||
DB::raw("(SELECT GROUP_CONCAT(roles.name SEPARATOR ';') as roles FROM model_has_roles INNER JOIN roles ON (model_has_roles.role_id = roles.id) WHERE model_has_roles.model_id = 1) as roles"),
|
||||
'users.is_partner',
|
||||
'users.is_employee',
|
||||
'users.is_prospect',
|
||||
'users.is_customer',
|
||||
'users.is_provider',
|
||||
'users.is_user',
|
||||
'users.status',
|
||||
DB::raw("CONCAT_WS(' ', created.name, created.last_name) AS creator"),
|
||||
'created.email AS creator_email',
|
||||
'users.created_by',
|
||||
'users.created_at',
|
||||
'users.updated_at',
|
||||
],
|
||||
'joins' => [
|
||||
[
|
||||
'table' => 'users as parent',
|
||||
'first' => 'users.parent_id',
|
||||
'second' => 'parent.id',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'users as agent',
|
||||
'first' => 'users.agent_id',
|
||||
'second' => 'agent.id',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'users as created',
|
||||
'first' => 'users.created_by',
|
||||
'second' => 'created.id',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'sat_codigo_postal',
|
||||
'first' => 'users.domicilio_fiscal',
|
||||
'second' => 'sat_codigo_postal.c_codigo_postal',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'sat_estado',
|
||||
'first' => 'sat_codigo_postal.c_estado',
|
||||
'second' => 'sat_estado.c_estado',
|
||||
'type' => 'leftJoin',
|
||||
'and' => [
|
||||
'sat_estado.c_pais = "MEX"',
|
||||
],
|
||||
],
|
||||
[
|
||||
'table' => 'sat_localidad',
|
||||
'first' => 'sat_codigo_postal.c_localidad',
|
||||
'second' => 'sat_localidad.c_localidad',
|
||||
'type' => 'leftJoin',
|
||||
'and' => [
|
||||
'sat_codigo_postal.c_estado = sat_localidad.c_estado',
|
||||
],
|
||||
],
|
||||
[
|
||||
'table' => 'sat_municipio',
|
||||
'first' => 'sat_codigo_postal.c_municipio',
|
||||
'second' => 'sat_municipio.c_municipio',
|
||||
'type' => 'leftJoin',
|
||||
'and' => [
|
||||
'sat_codigo_postal.c_estado = sat_municipio.c_estado',
|
||||
],
|
||||
],
|
||||
[
|
||||
'table' => 'sat_regimen_fiscal',
|
||||
'first' => 'users.c_regimen_fiscal',
|
||||
'second' => 'sat_regimen_fiscal.c_regimen_fiscal',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
[
|
||||
'table' => 'sat_uso_cfdi',
|
||||
'first' => 'users.c_uso_cfdi',
|
||||
'second' => 'sat_uso_cfdi.c_uso_cfdi',
|
||||
'type' => 'leftJoin',
|
||||
],
|
||||
],
|
||||
'filters' => [
|
||||
'search' => ['users.name', 'users.email', 'users.code', 'parent.name', 'created.name'],
|
||||
'search' => ['users.code', 'users.full_name', 'users.email', 'parent_name'],
|
||||
],
|
||||
'sort_column' => 'users.name',
|
||||
'sort_column' => 'users.full_name',
|
||||
'default_sort_order' => 'asc',
|
||||
];
|
||||
|
||||
@ -160,9 +82,7 @@ class UserController extends Controller
|
||||
//$user->stores()->attach($request->stores);
|
||||
|
||||
if ($request->file('photo')){
|
||||
$avatarImageService = new AvatarImageService();
|
||||
|
||||
$avatarImageService->updateProfilePhoto($user, $request->file('photo'));
|
||||
app(AvatarImageService::class)->updateProfilePhoto($user, $request->file('photo'));
|
||||
}
|
||||
|
||||
return response()->json(['success' => 'Se agrego correctamente el usuario']);
|
||||
@ -217,10 +137,9 @@ class UserController extends Controller
|
||||
//$user->stores()->sync($request->stores);
|
||||
|
||||
// Actualizamos foto de perfil
|
||||
if ($request->file('photo'))
|
||||
$avatarImageService = new AvatarImageService();
|
||||
|
||||
$avatarImageService->updateProfilePhoto($user, $request->file('photo'));
|
||||
if ($request->file('photo')){
|
||||
app(AvatarImageService::class)->updateProfilePhoto($user, $request->file('photo'));
|
||||
}
|
||||
|
||||
return response()->json(['success' => 'Se guardo correctamente los cambios.']);
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Koneko\VuexyAdmin\Services\AvatarInitialsService;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
|
||||
class UserProfileController extends Controller
|
||||
{
|
||||
@ -35,10 +34,8 @@ class UserProfileController extends Controller
|
||||
$background = $request->get('background', 'EBF4FF');
|
||||
$size = $request->get('size', 100);
|
||||
|
||||
$avatarService = new AvatarInitialsService();
|
||||
|
||||
try {
|
||||
return $avatarService->getAvatarImage($name, $color, $background, $size);
|
||||
return app(AvatarInitialsService::class)->getAvatarImage($name, $color, $background, $size);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// String base64 de una imagen PNG transparente de 1x1 píxel
|
||||
|
100
Http/Controllers/VuexyAdminController.php
Normal file
100
Http/Controllers/VuexyAdminController.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
use Koneko\VuexyAdmin\Services\SettingsService;
|
||||
use Koneko\VuexyAdmin\Services\VuexyAdminService;
|
||||
|
||||
/**
|
||||
* Controlador para la gestión de funcionalidades administrativas de Vuexy
|
||||
*/
|
||||
class VuexyAdminController extends Controller
|
||||
{
|
||||
/**
|
||||
* Muestra la vista de configuraciones generales
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function GeneralSettings()
|
||||
{
|
||||
return view('vuexy-admin::general-settings.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Muestra la vista de configuraciones SMTP
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function smtpSettings()
|
||||
{
|
||||
return view('vuexy-admin::sendmail-settings.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Muestra la vista de configuraciones de interfaz
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function VuexyInterfaceSettings()
|
||||
{
|
||||
return view('vuexy-admin::interface-settings.index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Realiza búsqueda en la barra de navegación
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
* @throws \Illuminate\Http\Exceptions\HttpResponseException
|
||||
*/
|
||||
public function searchNavbar()
|
||||
{
|
||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
||||
|
||||
return response()->json(app(VuexyAdminService::class)->getVuexySearchData());
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza los enlaces rápidos del usuario
|
||||
*
|
||||
* @param Request $request Datos de la solicitud
|
||||
* @return void
|
||||
* @throws \Illuminate\Http\Exceptions\HttpResponseException
|
||||
* @throws \Illuminate\Validation\ValidationException
|
||||
*/
|
||||
public function quickLinksUpdate(Request $request)
|
||||
{
|
||||
abort_if(!request()->expectsJson(), 403, __('errors.ajax_only'));
|
||||
|
||||
$validated = $request->validate([
|
||||
'action' => 'required|in:update,remove',
|
||||
'route' => 'required|string',
|
||||
]);
|
||||
|
||||
$quickLinks = Setting::where('key', 'quicklinks')
|
||||
->where('user_id', Auth::user()->id)
|
||||
->first();
|
||||
|
||||
$quickLinks = $quickLinks ? json_decode($quickLinks->value, true) : [];
|
||||
|
||||
if ($validated['action'] === 'update') {
|
||||
// Verificar si ya existe
|
||||
if (!in_array($validated['route'], $quickLinks)) {
|
||||
$quickLinks[] = $validated['route'];
|
||||
}
|
||||
|
||||
} elseif ($validated['action'] === 'remove') {
|
||||
// Eliminar la ruta si existe
|
||||
$quickLinks = array_filter($quickLinks, function ($route) use ($validated) {
|
||||
return $route !== $validated['route'];
|
||||
});
|
||||
}
|
||||
|
||||
app(SettingsService::class)->set('quicklinks', json_encode($quickLinks), Auth::user()->id, 'vuexy-admin');
|
||||
|
||||
VuexyAdminService::clearQuickLinksCache();
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Koneko\VuexyAdmin\Services\AdminSettingsService;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
|
||||
class ApplicationSettings extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
private $targetNotify = "#application-settings-card .notification-container";
|
||||
|
||||
public $admin_app_name,
|
||||
$admin_image_logo,
|
||||
$admin_image_logo_dark;
|
||||
|
||||
public $upload_image_logo,
|
||||
$upload_image_logo_dark;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
public function loadSettings($clearcache = false)
|
||||
{
|
||||
$this->upload_image_logo = null;
|
||||
$this->upload_image_logo_dark = null;
|
||||
|
||||
$adminTemplateService = app(AdminTemplateService::class);
|
||||
|
||||
if ($clearcache) {
|
||||
$adminTemplateService->clearAdminVarsCache();
|
||||
}
|
||||
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = $adminTemplateService->getAdminVars();
|
||||
|
||||
$this->admin_app_name = $settings['app_name'];
|
||||
$this->admin_image_logo = $settings['image_logo']['large'];
|
||||
$this->admin_image_logo_dark = $settings['image_logo']['large_dark'];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'admin_app_name' => 'required|string|max:255',
|
||||
'upload_image_logo' => 'nullable|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||
'upload_image_logo_dark' => 'nullable|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||
]);
|
||||
|
||||
$adminSettingsService = app(AdminSettingsService::class);
|
||||
|
||||
// Guardar título del App en configuraciones
|
||||
$adminSettingsService->updateSetting('admin_app_name', $this->admin_app_name);
|
||||
|
||||
// Procesar favicon si se ha cargado una imagen
|
||||
if ($this->upload_image_logo) {
|
||||
$adminSettingsService->processAndSaveImageLogo($this->upload_image_logo);
|
||||
}
|
||||
|
||||
if ($this->upload_image_logo_dark) {
|
||||
$adminSettingsService->processAndSaveImageLogo($this->upload_image_logo_dark, 'dark');
|
||||
}
|
||||
|
||||
$this->loadSettings(true);
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.'
|
||||
);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.admin-settings.application-settings');
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
use Koneko\VuexyAdmin\Services\GlobalSettingsService;
|
||||
|
||||
class InterfaceSettings extends Component
|
||||
{
|
||||
private $targetNotify = "#interface-settings-card .notification-container";
|
||||
|
||||
public $vuexy_myLayout,
|
||||
$vuexy_myTheme,
|
||||
$vuexy_myStyle,
|
||||
$vuexy_hasCustomizer,
|
||||
$vuexy_displayCustomizer,
|
||||
$vuexy_contentLayout,
|
||||
$vuexy_navbarType,
|
||||
$vuexy_footerFixed,
|
||||
$vuexy_menuFixed,
|
||||
$vuexy_menuCollapsed,
|
||||
$vuexy_headerType,
|
||||
$vuexy_showDropdownOnHover,
|
||||
$vuexy_authViewMode,
|
||||
$vuexy_maxQuickLinks;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
|
||||
public function loadSettings()
|
||||
{
|
||||
$adminTemplateService = app(AdminTemplateService::class);
|
||||
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = $adminTemplateService->getVuexyCustomizerVars();
|
||||
|
||||
$this->vuexy_myLayout = $settings['myLayout'];
|
||||
$this->vuexy_myTheme = $settings['myTheme'];
|
||||
$this->vuexy_myStyle = $settings['myStyle'];
|
||||
$this->vuexy_hasCustomizer = $settings['hasCustomizer'];
|
||||
$this->vuexy_displayCustomizer = $settings['displayCustomizer'];
|
||||
$this->vuexy_contentLayout = $settings['contentLayout'];
|
||||
$this->vuexy_navbarType = $settings['navbarType'];
|
||||
$this->vuexy_footerFixed = $settings['footerFixed'];
|
||||
$this->vuexy_menuFixed = $settings['menuFixed'];
|
||||
$this->vuexy_menuCollapsed = $settings['menuCollapsed'];
|
||||
$this->vuexy_headerType = $settings['headerType'];
|
||||
$this->vuexy_showDropdownOnHover = $settings['showDropdownOnHover'];
|
||||
$this->vuexy_authViewMode = $settings['authViewMode'];
|
||||
$this->vuexy_maxQuickLinks = $settings['maxQuickLinks'];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'vuexy_maxQuickLinks' => 'required|integer|min:2|max:20',
|
||||
]);
|
||||
|
||||
$globalSettingsService = app(GlobalSettingsService::class);
|
||||
|
||||
// Guardar configuraciones
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.myLayout', $this->vuexy_myLayout);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.myTheme', $this->vuexy_myTheme);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.myStyle', $this->vuexy_myStyle);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.hasCustomizer', $this->vuexy_hasCustomizer);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.displayCustomizer', $this->vuexy_displayCustomizer);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.contentLayout', $this->vuexy_contentLayout);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.navbarType', $this->vuexy_navbarType);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.footerFixed', $this->vuexy_footerFixed);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.menuFixed', $this->vuexy_menuFixed);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.menuCollapsed', $this->vuexy_menuCollapsed);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.headerType', $this->vuexy_headerType);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.showDropdownOnHover', $this->vuexy_showDropdownOnHover);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.authViewMode', $this->vuexy_authViewMode);
|
||||
$globalSettingsService->updateSetting('config.vuexy.custom.maxQuickLinks', $this->vuexy_maxQuickLinks);
|
||||
|
||||
$globalSettingsService->clearSystemConfigCache();
|
||||
|
||||
// Refrescar el componente actual
|
||||
$this->dispatch('clearLocalStoregeTemplateCustomizer');
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.',
|
||||
deferReload: true
|
||||
);
|
||||
}
|
||||
|
||||
public function clearCustomConfig()
|
||||
{
|
||||
$globalSettingsService = app(GlobalSettingsService::class);
|
||||
|
||||
$globalSettingsService->clearVuexyConfig();
|
||||
|
||||
// Refrescar el componente actual
|
||||
$this->dispatch('clearLocalStoregeTemplateCustomizer');
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.',
|
||||
deferReload: true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.admin-settings.interface-settings');
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\GlobalSettingsService;
|
||||
|
||||
class MailSenderResponseSettings extends Component
|
||||
{
|
||||
private $targetNotify = "#mail-sender-response-settings-card .notification-container";
|
||||
|
||||
public $from_address,
|
||||
$from_name,
|
||||
$reply_to_method,
|
||||
$reply_to_email,
|
||||
$reply_to_name;
|
||||
|
||||
protected $listeners = ['saveMailSenderResponseSettings' => 'save'];
|
||||
|
||||
const REPLY_EMAIL_CREATOR = 1;
|
||||
const REPLY_EMAIL_SENDER = 2;
|
||||
const REPLY_EMAIL_CUSTOM = 3;
|
||||
|
||||
public $reply_email_options = [
|
||||
self::REPLY_EMAIL_CREATOR => 'Responder al creador del documento',
|
||||
self::REPLY_EMAIL_SENDER => 'Responder a quien envía el documento',
|
||||
self::REPLY_EMAIL_CUSTOM => 'Definir dirección de correo electrónico',
|
||||
];
|
||||
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
|
||||
public function loadSettings()
|
||||
{
|
||||
$globalSettingsService = app(GlobalSettingsService::class);
|
||||
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = $globalSettingsService->getMailSystemConfig();
|
||||
|
||||
$this->from_address = $settings['from']['address'];
|
||||
$this->from_name = $settings['from']['name'];
|
||||
$this->reply_to_method = $settings['reply_to']['method'];
|
||||
$this->reply_to_email = $settings['reply_to']['email'];
|
||||
$this->reply_to_name = $settings['reply_to']['name'];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'from_address' => 'required|email',
|
||||
'from_name' => 'required|string|max:255',
|
||||
'reply_to_method' => 'required|string|max:255',
|
||||
], [
|
||||
'from_address.required' => 'El campo de correo electrónico es obligatorio.',
|
||||
'from_address.email' => 'El formato del correo electrónico no es válido.',
|
||||
'from_name.required' => 'El nombre es obligatorio.',
|
||||
'from_name.string' => 'El nombre debe ser una cadena de texto.',
|
||||
'from_name.max' => 'El nombre no puede tener más de 255 caracteres.',
|
||||
'reply_to_method.required' => 'El método de respuesta es obligatorio.',
|
||||
'reply_to_method.string' => 'El método de respuesta debe ser una cadena de texto.',
|
||||
'reply_to_method.max' => 'El método de respuesta no puede tener más de 255 caracteres.',
|
||||
]);
|
||||
|
||||
if ($this->reply_to_method == self::REPLY_EMAIL_CUSTOM) {
|
||||
$this->validate([
|
||||
'reply_to_email' => ['required', 'email'],
|
||||
'reply_to_name' => ['required', 'string', 'max:255'],
|
||||
], [
|
||||
'reply_to_email.required' => 'El correo de respuesta es obligatorio.',
|
||||
'reply_to_email.email' => 'El formato del correo de respuesta no es válido.',
|
||||
'reply_to_name.required' => 'El nombre de respuesta es obligatorio.',
|
||||
'reply_to_name.string' => 'El nombre de respuesta debe ser una cadena de texto.',
|
||||
'reply_to_name.max' => 'El nombre de respuesta no puede tener más de 255 caracteres.',
|
||||
]);
|
||||
}
|
||||
|
||||
$globalSettingsService = app(GlobalSettingsService::class);
|
||||
|
||||
// Guardar título del App en configuraciones
|
||||
$globalSettingsService->updateSetting('mail.from.address', $this->from_address);
|
||||
$globalSettingsService->updateSetting('mail.from.name', $this->from_name);
|
||||
$globalSettingsService->updateSetting('mail.reply_to.method', $this->reply_to_method);
|
||||
$globalSettingsService->updateSetting('mail.reply_to.email', $this->reply_to_method == self::REPLY_EMAIL_CUSTOM ? $this->reply_to_email : '');
|
||||
$globalSettingsService->updateSetting('mail.reply_to.name', $this->reply_to_method == self::REPLY_EMAIL_CUSTOM ? $this->reply_to_name : '');
|
||||
|
||||
$globalSettingsService->clearMailSystemConfigCache();
|
||||
|
||||
$this->loadSettings();
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.',
|
||||
);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.admin-settings.mail-sender-response-settings');
|
||||
}
|
||||
}
|
@ -128,27 +128,6 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
||||
*/
|
||||
abstract protected function model(): string;
|
||||
|
||||
/**
|
||||
* Define los campos del formulario.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
abstract protected function fields(): array;
|
||||
|
||||
/**
|
||||
* Retorna los valores por defecto para los campos del formulario.
|
||||
*
|
||||
* @return array<string, mixed> Valores predeterminados.
|
||||
*/
|
||||
abstract protected function defaults(): array;
|
||||
|
||||
/**
|
||||
* Campo que se debe enfocar cuando se abra el formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function focusOnOpen(): string;
|
||||
|
||||
/**
|
||||
* Define reglas de validación dinámicas según el modo del formulario.
|
||||
*
|
||||
@ -157,13 +136,6 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
||||
*/
|
||||
abstract protected function dynamicRules(string $mode): array;
|
||||
|
||||
/**
|
||||
* Devuelve las opciones que se mostrarán en los selectores del formulario.
|
||||
*
|
||||
* @return array<string, mixed> Opciones para los campos del formulario.
|
||||
*/
|
||||
abstract protected function options(): array;
|
||||
|
||||
/**
|
||||
* Retorna la ruta de la vista asociada al formulario.
|
||||
*
|
||||
@ -171,6 +143,50 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
||||
*/
|
||||
abstract protected function viewPath(): string;
|
||||
|
||||
// ===================== CONFIGURACIÓN =====================
|
||||
|
||||
/**
|
||||
* Define los campos del formulario.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function fields(): array
|
||||
{
|
||||
return (new ($this->model()))->getFillable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna los valores por defecto para los campos del formulario.
|
||||
*
|
||||
* @return array<string, mixed> Valores predeterminados.
|
||||
*/
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Campo que se debe enfocar cuando se abra el formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function focusOnOpen(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
// ===================== OPCIONES =====================
|
||||
|
||||
/**
|
||||
* Devuelve las opciones que se mostrarán en los selectores del formulario.
|
||||
*
|
||||
* @return array<string, mixed> Opciones para los campos del formulario.
|
||||
*/
|
||||
protected function options(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// ===================== VALIDACIONES =====================
|
||||
|
||||
protected function attributes(): array
|
||||
@ -198,7 +214,7 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
||||
|
||||
$model = new ($this->model());
|
||||
|
||||
$this->tagName = $model->tagName;
|
||||
$this->tagName = Str::camel($model->tagName);
|
||||
$this->columnNameLabel = $model->columnNameLabel;
|
||||
$this->singularName = $model->singularName;
|
||||
$this->offcanvasId = 'offcanvas' . ucfirst(Str::camel($model->tagName));
|
||||
@ -288,6 +304,9 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
||||
$model = $this->model()::find($id);
|
||||
|
||||
if ($model) {
|
||||
|
||||
dd($this->fields());
|
||||
|
||||
$data = $model->only(['id', ...$this->fields()]);
|
||||
|
||||
$this->applyCasts($data);
|
||||
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Permissions;
|
||||
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class PermissionIndex extends Component
|
||||
{
|
||||
public $roles_html_select;
|
||||
public $rows_roles;
|
||||
|
||||
public function render()
|
||||
{
|
||||
// Generamos Select y estilos HTML de roles
|
||||
$this->roles_html_select = "<select id=\"UserRole\" class=\"form-select text-capitalize\"><option value=\"\"> Selecciona un rol </option>";
|
||||
|
||||
foreach (Role::all() as $role) {
|
||||
$this->rows_roles[$role->name] = "<span class=\"badge bg-label-{$role->style} m-1\">{$role->name}</span>";
|
||||
$this->roles_html_select .= "<option value=\"{$role->name}\" class=\"text-capitalize\">{$role->name}</option>";
|
||||
}
|
||||
|
||||
$this->roles_html_select .= "</select>";
|
||||
|
||||
return view('vuexy-admin::livewire.permissions.index');
|
||||
}
|
||||
}
|
160
Livewire/Permissions/PermissionOffCanvasForm.php
Normal file
160
Livewire/Permissions/PermissionOffCanvasForm.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Permissions;
|
||||
|
||||
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormOffCanvasComponent;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
/**
|
||||
* Class PermissionOffcanvasForm
|
||||
*
|
||||
* Componente Livewire para gestionar permisos.
|
||||
* Extiende AbstractFormOffCanvasComponent para proporcionar una interfaz
|
||||
* eficiente para la gestión de permisos en el ERP.
|
||||
*
|
||||
* @package App\Http\Livewire\Forms
|
||||
*/
|
||||
class PermissionOffcanvasForm extends AbstractFormOffCanvasComponent
|
||||
{
|
||||
/**
|
||||
* Propiedades del formulario.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
public $id, $name, $group_name, $sub_group_name, $action, $guard_name = 'web';
|
||||
|
||||
/**
|
||||
* Eventos de escucha de Livewire.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listeners = [
|
||||
'editPermission' => 'loadFormModel',
|
||||
'confirmDeletionPermission' => 'loadFormModelForDeletion',
|
||||
];
|
||||
|
||||
/**
|
||||
* Define el modelo Eloquent asociado con el formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function model(): string
|
||||
{
|
||||
return Permission::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Valores por defecto para el formulario.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'guard_name' => 'web',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Campo que se debe enfocar cuando se abra el formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function focusOnOpen(): string
|
||||
{
|
||||
return 'name';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define reglas de validación dinámicas basadas en el modo actual.
|
||||
*
|
||||
* @param string $mode El modo actual del formulario ('create', 'edit', 'delete').
|
||||
* @return array
|
||||
*/
|
||||
protected function dynamicRules(string $mode): array
|
||||
{
|
||||
switch ($mode) {
|
||||
case 'create':
|
||||
case 'edit':
|
||||
return [
|
||||
'name' => ['required', 'string', Rule::unique('permissions', 'name')->ignore($this->id)],
|
||||
'group_name' => ['nullable', 'string'],
|
||||
'sub_group_name' => ['nullable', 'string'],
|
||||
'action' => ['nullable', 'string'],
|
||||
'guard_name' => ['required', 'string'],
|
||||
];
|
||||
|
||||
case 'delete':
|
||||
return ['confirmDeletion' => 'accepted'];
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Define los atributos personalizados para los errores de validación.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nombre del permiso',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Define los mensajes de error personalizados para la validación.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'El nombre del permiso es obligatorio.',
|
||||
'name.unique' => 'Este permiso ya existe.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga el formulario con datos de un permiso específico.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function loadFormModel($id): void
|
||||
{
|
||||
parent::loadFormModel($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga el formulario para eliminar un permiso específico.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function loadFormModelForDeletion($id): void
|
||||
{
|
||||
parent::loadFormModelForDeletion($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define las opciones de los selectores desplegables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function options(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruta de la vista asociada con este formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function viewPath(): string
|
||||
{
|
||||
return 'vuexy-admin::livewire.permissions.offcanvas-form';
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Permissions;
|
||||
|
||||
use Livewire\Component;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
class Permissions extends Component
|
||||
{
|
||||
public $permissionName;
|
||||
|
||||
public function createPermission()
|
||||
{
|
||||
$this->validate([
|
||||
'permissionName' => 'required|unique:permissions,name'
|
||||
]);
|
||||
|
||||
Permission::create(['name' => $this->permissionName]);
|
||||
session()->flash('message', 'Permiso creado con éxito.');
|
||||
$this->reset('permissionName');
|
||||
}
|
||||
|
||||
public function deletePermission($id)
|
||||
{
|
||||
Permission::find($id)->delete();
|
||||
session()->flash('message', 'Permiso eliminado.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.permissions', [
|
||||
'permissions' => Permission::all()
|
||||
]);
|
||||
}
|
||||
}
|
98
Livewire/Permissions/PermissionsIndex.php
Normal file
98
Livewire/Permissions/PermissionsIndex.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Permissions;
|
||||
|
||||
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Listado de Permisos, extiende de la clase base AbstractIndexComponent
|
||||
* para reutilizar la lógica de configuración y renderizado de tablas.
|
||||
*/
|
||||
class PermissionsIndex extends AbstractIndexComponent
|
||||
{
|
||||
/**
|
||||
* Define la clase del modelo a usar en este Index.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function model(): string
|
||||
{
|
||||
return Permission::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna las columnas de la tabla.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function columns(): array
|
||||
{
|
||||
return [
|
||||
'action' => 'Acciones',
|
||||
'name' => 'Nombre del Permiso',
|
||||
'group_name' => 'Grupo',
|
||||
'sub_group_name' => 'Subgrupo',
|
||||
'action' => 'Acción',
|
||||
'guard_name' => 'Guard',
|
||||
'created_at' => 'Creado',
|
||||
'updated_at' => 'Modificado',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna el formato para cada columna.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function format(): array
|
||||
{
|
||||
return [
|
||||
'action' => [
|
||||
'formatter' => 'storeActionFormatter',
|
||||
'onlyFormatter' => true,
|
||||
],
|
||||
'name' => [
|
||||
'switchable' => false,
|
||||
],
|
||||
'created_at' => [
|
||||
'formatter' => 'whitespaceNowrapFormatter',
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
'updated_at' => [
|
||||
'formatter' => 'whitespaceNowrapFormatter',
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sobrescribe la configuración base de la tabla.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function bootstraptableConfig(): array
|
||||
{
|
||||
return array_merge(parent::bootstraptableConfig(), [
|
||||
'sortName' => 'name',
|
||||
'exportFileName' => 'Permisos',
|
||||
'showFullscreen' => false,
|
||||
'showPaginationSwitch'=> false,
|
||||
'showRefresh' => false,
|
||||
'pagination' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna la vista a renderizar por este componente.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function viewPath(): string
|
||||
{
|
||||
return 'vuexy-admin::livewire.permissions.index';
|
||||
}
|
||||
}
|
118
Livewire/Profile/DeleteUserForm.php
Normal file
118
Livewire/Profile/DeleteUserForm.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Profile;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class DeleteUserForm extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* The new avatar for the user.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $photo;
|
||||
|
||||
/**
|
||||
* Determine if the verification email was sent.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $verificationLinkSent = false;
|
||||
|
||||
/**
|
||||
* Prepare the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$this->state = array_merge([
|
||||
'email' => $user->email,
|
||||
], $user->withoutRelations()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*
|
||||
* @param \Laravel\Fortify\Contracts\UpdatesUserProfileInformation $updater
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update(
|
||||
Auth::user(),
|
||||
$this->photo
|
||||
? array_merge($this->state, ['photo' => $this->photo])
|
||||
: $this->state
|
||||
);
|
||||
|
||||
if (isset($this->photo)) {
|
||||
return redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user's profile photo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
Auth::user()->deleteProfilePhoto();
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent the email verification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailVerification()
|
||||
{
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
$this->verificationLinkSent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.profile.update-profile-information-form');
|
||||
}
|
||||
}
|
118
Livewire/Profile/LogoutOtherBrowser.php
Normal file
118
Livewire/Profile/LogoutOtherBrowser.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Profile;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class LogoutOtherBrowser extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* The new avatar for the user.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $photo;
|
||||
|
||||
/**
|
||||
* Determine if the verification email was sent.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $verificationLinkSent = false;
|
||||
|
||||
/**
|
||||
* Prepare the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$this->state = array_merge([
|
||||
'email' => $user->email,
|
||||
], $user->withoutRelations()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*
|
||||
* @param \Laravel\Fortify\Contracts\UpdatesUserProfileInformation $updater
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update(
|
||||
Auth::user(),
|
||||
$this->photo
|
||||
? array_merge($this->state, ['photo' => $this->photo])
|
||||
: $this->state
|
||||
);
|
||||
|
||||
if (isset($this->photo)) {
|
||||
return redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user's profile photo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
Auth::user()->deleteProfilePhoto();
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent the email verification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailVerification()
|
||||
{
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
$this->verificationLinkSent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.profile.update-profile-information-form');
|
||||
}
|
||||
}
|
118
Livewire/Profile/TwoFactorAuthenticationForm.php
Normal file
118
Livewire/Profile/TwoFactorAuthenticationForm.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Profile;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class TwoFactorAuthenticationForm extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* The new avatar for the user.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $photo;
|
||||
|
||||
/**
|
||||
* Determine if the verification email was sent.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $verificationLinkSent = false;
|
||||
|
||||
/**
|
||||
* Prepare the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$this->state = array_merge([
|
||||
'email' => $user->email,
|
||||
], $user->withoutRelations()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*
|
||||
* @param \Laravel\Fortify\Contracts\UpdatesUserProfileInformation $updater
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update(
|
||||
Auth::user(),
|
||||
$this->photo
|
||||
? array_merge($this->state, ['photo' => $this->photo])
|
||||
: $this->state
|
||||
);
|
||||
|
||||
if (isset($this->photo)) {
|
||||
return redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user's profile photo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
Auth::user()->deleteProfilePhoto();
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent the email verification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailVerification()
|
||||
{
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
$this->verificationLinkSent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.profile.update-profile-information-form');
|
||||
}
|
||||
}
|
118
Livewire/Profile/UpdatePasswordForm.php
Normal file
118
Livewire/Profile/UpdatePasswordForm.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Profile;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class UpdatePasswordForm extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* The new avatar for the user.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $photo;
|
||||
|
||||
/**
|
||||
* Determine if the verification email was sent.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $verificationLinkSent = false;
|
||||
|
||||
/**
|
||||
* Prepare the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$this->state = array_merge([
|
||||
'email' => $user->email,
|
||||
], $user->withoutRelations()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*
|
||||
* @param \Laravel\Fortify\Contracts\UpdatesUserProfileInformation $updater
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update(
|
||||
Auth::user(),
|
||||
$this->photo
|
||||
? array_merge($this->state, ['photo' => $this->photo])
|
||||
: $this->state
|
||||
);
|
||||
|
||||
if (isset($this->photo)) {
|
||||
return redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user's profile photo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
Auth::user()->deleteProfilePhoto();
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent the email verification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailVerification()
|
||||
{
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
$this->verificationLinkSent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.profile.update-profile-information-form');
|
||||
}
|
||||
}
|
118
Livewire/Profile/UpdateProfileInformationForm.php
Normal file
118
Livewire/Profile/UpdateProfileInformationForm.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Profile;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Laravel\Fortify\Contracts\UpdatesUserProfileInformation;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
class UpdateProfileInformationForm extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
/**
|
||||
* The component's state.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $state = [];
|
||||
|
||||
/**
|
||||
* The new avatar for the user.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $photo;
|
||||
|
||||
/**
|
||||
* Determine if the verification email was sent.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $verificationLinkSent = false;
|
||||
|
||||
/**
|
||||
* Prepare the component.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$this->state = array_merge([
|
||||
'email' => $user->email,
|
||||
], $user->withoutRelations()->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the user's profile information.
|
||||
*
|
||||
* @param \Laravel\Fortify\Contracts\UpdatesUserProfileInformation $updater
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function updateProfileInformation(UpdatesUserProfileInformation $updater)
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
|
||||
$updater->update(
|
||||
Auth::user(),
|
||||
$this->photo
|
||||
? array_merge($this->state, ['photo' => $this->photo])
|
||||
: $this->state
|
||||
);
|
||||
|
||||
if (isset($this->photo)) {
|
||||
return redirect()->route('profile.show');
|
||||
}
|
||||
|
||||
$this->dispatch('saved');
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user's profile photo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
Auth::user()->deleteProfilePhoto();
|
||||
|
||||
$this->dispatch('refresh-navigation-menu');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent the email verification.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function sendEmailVerification()
|
||||
{
|
||||
Auth::user()->sendEmailVerificationNotification();
|
||||
|
||||
$this->verificationLinkSent = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current user of the application.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getUserProperty()
|
||||
{
|
||||
return Auth::user();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.profile.update-profile-information-form');
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ use Livewire\WithPagination;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
class RoleIndex extends Component
|
||||
class RolesIndex extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
@ -54,8 +54,8 @@ class RoleIndex extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.roles', [
|
||||
'index' => Role::paginate(10)
|
||||
return view('vuexy-admin::livewire.roles.index', [
|
||||
'roles' => Role::paginate(10)
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class UserIndex extends Component
|
||||
{
|
||||
public $statuses;
|
||||
public $status_options;
|
||||
|
||||
public $rows_roles = [];
|
||||
public $roles_options = [];
|
||||
public $roles_html_select;
|
||||
|
||||
public $total, $enabled, $disabled;
|
||||
|
||||
public $indexAlert;
|
||||
|
||||
public $userId, $name, $email, $password, $roles, $status, $photo, $src_photo;
|
||||
public $modalTitle;
|
||||
public $btnSubmitTxt;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->modalTitle = 'Crear usuario nuevo';
|
||||
$this->btnSubmitTxt = 'Crear usuario';
|
||||
|
||||
$this->statuses = [
|
||||
User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]],
|
||||
User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]],
|
||||
User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]],
|
||||
];
|
||||
|
||||
$roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get();
|
||||
|
||||
$this->roles_html_select = "<select id=\"UserRole\" class=\"form-select text-capitalize\"><option value=\"\"> Selecciona un rol </option>";
|
||||
|
||||
foreach ($roles as $role) {
|
||||
$this->rows_roles[$role->name] = "<span class=\"badge bg-label-" . $role->style . " mx-1\">" . $role->name . "</span>";
|
||||
|
||||
if (Auth::user()->hasRole('SuperAdmin') || $role->name != 'SuperAdmin') {
|
||||
$this->roles_html_select .= "<option value=\"" . $role->name . "\" class=\"text-capitalize\">" . $role->name . "</option>";
|
||||
$this->roles_options[$role->name] = $role->name;
|
||||
}
|
||||
}
|
||||
|
||||
$this->roles_html_select .= "</select>";
|
||||
|
||||
$this->status_options = [
|
||||
User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED],
|
||||
User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED],
|
||||
];
|
||||
}
|
||||
|
||||
public function countUsers()
|
||||
{
|
||||
$this->total = User::count();
|
||||
$this->enabled = User::where('status', User::STATUS_ENABLED)->count();
|
||||
$this->disabled = User::where('status', User::STATUS_DISABLED)->count();
|
||||
}
|
||||
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$user = User::findOrFail($id);
|
||||
|
||||
$this->indexAlert = '';
|
||||
$this->modalTitle = 'Editar usuario: ' . $id;
|
||||
$this->btnSubmitTxt = 'Guardar cambios';
|
||||
|
||||
$this->userId = $user->id;
|
||||
$this->name = $user->name;
|
||||
$this->email = $user->email;
|
||||
$this->password = '';
|
||||
$this->roles = $user->roles->pluck('name')->toArray();
|
||||
$this->src_photo = $user->profile_photo_url;
|
||||
$this->status = $user->status;
|
||||
|
||||
$this->dispatch('openModal');
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
$user = User::find($id);
|
||||
|
||||
if ($user) {
|
||||
// Eliminar la imagen de perfil si existe
|
||||
if ($user->profile_photo_path)
|
||||
Storage::disk('public')->delete($user->profile_photo_path);
|
||||
|
||||
// Eliminar el usuario
|
||||
$user->delete();
|
||||
|
||||
$this->indexAlert = '<div class="alert alert-warning alert-dismissible" role="alert">Se eliminó correctamente el usuario.<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||
|
||||
$this->dispatch('refreshUserCount');
|
||||
$this->dispatch('afterDelete');
|
||||
} else {
|
||||
$this->indexAlert = '<div class="alert alert-danger alert-dismissible" role="alert">Usuario no encontrado.<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button></div>';
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.users.index', [
|
||||
'users' => User::paginate(10),
|
||||
]);
|
||||
}
|
||||
}
|
@ -2,14 +2,8 @@
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormOffCanvasComponent;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyContacts\Services\{ContactCatalogService,ConstanciaFiscalService,FacturaXmlService};
|
||||
use Koneko\VuexyStoreManager\Services\StoreCatalogService;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
/**
|
||||
* Class UserOffCanvasForm
|
||||
@ -22,40 +16,15 @@ use Livewire\WithFileUploads;
|
||||
*/
|
||||
class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $doc_file;
|
||||
public $dropzoneVisible = true;
|
||||
|
||||
|
||||
/**
|
||||
* Propiedades del formulario relacionadas con el usuario.
|
||||
*/
|
||||
public $code,
|
||||
$parent_id,
|
||||
$name,
|
||||
$last_name,
|
||||
$email,
|
||||
$company,
|
||||
$rfc,
|
||||
$nombre_fiscal,
|
||||
$tipo_persona,
|
||||
$c_regimen_fiscal,
|
||||
$domicilio_fiscal,
|
||||
$is_partner,
|
||||
$is_employee,
|
||||
$is_prospect,
|
||||
$is_customer,
|
||||
$is_provider,
|
||||
$status;
|
||||
|
||||
/**
|
||||
* Listas de opciones para selects en el formulario.
|
||||
*/
|
||||
public $store_options = [],
|
||||
$work_center_options = [],
|
||||
$manager_options = [];
|
||||
|
||||
/**
|
||||
* Eventos de escucha de Livewire.
|
||||
*
|
||||
@ -85,28 +54,6 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
return User::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define los campos del formulario.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected function fields(): array
|
||||
{
|
||||
return (new User())->getFillable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Valores por defecto para el formulario.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Campo que se debe enfocar cuando se abra el formulario.
|
||||
*
|
||||
@ -117,6 +64,8 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
return 'name';
|
||||
}
|
||||
|
||||
// ===================== VALIDACIONES =====================
|
||||
|
||||
/**
|
||||
* Define reglas de validación dinámicas basadas en el modo actual.
|
||||
*
|
||||
@ -129,10 +78,8 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
case 'create':
|
||||
case 'edit':
|
||||
return [
|
||||
'code' => ['required', 'string', 'max:16', Rule::unique('contact', 'code')->ignore($this->id)],
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
|
||||
'name' => ['required', 'string', 'max:96'],
|
||||
'notes' => ['nullable', 'string', 'max:1024'],
|
||||
'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'],
|
||||
];
|
||||
|
||||
case 'delete':
|
||||
@ -145,8 +92,6 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== VALIDACIONES =====================
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
@ -168,121 +113,10 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
protected function messages(): array
|
||||
{
|
||||
return [
|
||||
'code.unique' => 'Este código ya está en uso por otro usuario.',
|
||||
'name.required' => 'El nombre del usuario es obligatorio.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga el formulario con datos del usuario y actualiza las opciones dinámicas.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function loadFormModel($id): void
|
||||
{
|
||||
parent::loadFormModel($id);
|
||||
|
||||
$this->work_center_options = $this->store_id
|
||||
? DB::table('store_work_centers')
|
||||
->where('store_id', $this->store_id)
|
||||
->pluck('name', 'id')
|
||||
->toArray()
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga el formulario para eliminar un usuario, actualizando las opciones necesarias.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function loadFormModelForDeletion($id): void
|
||||
{
|
||||
parent::loadFormModelForDeletion($id);
|
||||
|
||||
$this->work_center_options = DB::table('store_work_centers')
|
||||
->where('store_id', $this->store_id)
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define las opciones de los selectores desplegables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function options(): array
|
||||
{
|
||||
$storeCatalogService = app(StoreCatalogService::class);
|
||||
$contactCatalogService = app(ContactCatalogService::class);
|
||||
|
||||
return [
|
||||
'store_options' => $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]),
|
||||
'manager_options' => $contactCatalogService->searchCatalog('users', '', ['limit' => -1]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa el documento recibido (CFDI XML o Constancia PDF).
|
||||
*/
|
||||
public function processDocument()
|
||||
{
|
||||
// Verificamos si el archivo es válido
|
||||
if (!$this->doc_file instanceof UploadedFile) {
|
||||
return $this->addError('doc_file', 'No se pudo recibir el archivo.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Validar tipo de archivo
|
||||
$this->validate([
|
||||
'doc_file' => 'required|mimes:pdf,xml|max:2048'
|
||||
]);
|
||||
|
||||
|
||||
// **Detectar el tipo de documento**
|
||||
$extension = strtolower($this->doc_file->getClientOriginalExtension());
|
||||
|
||||
// **Procesar según el tipo de archivo**
|
||||
switch ($extension) {
|
||||
case 'xml':
|
||||
$service = new FacturaXmlService();
|
||||
$data = $service->processUploadedFile($this->doc_file);
|
||||
break;
|
||||
|
||||
case 'pdf':
|
||||
$service = new ConstanciaFiscalService();
|
||||
$data = $service->extractData($this->doc_file);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Formato de archivo no soportado.");
|
||||
}
|
||||
|
||||
dd($data);
|
||||
|
||||
// **Asignar los valores extraídos al formulario**
|
||||
$this->rfc = $data['rfc'] ?? null;
|
||||
$this->name = $data['name'] ?? null;
|
||||
$this->email = $data['email'] ?? null;
|
||||
$this->tel = $data['telefono'] ?? null;
|
||||
//$this->direccion = $data['domicilio_fiscal'] ?? null;
|
||||
|
||||
// Ocultar el Dropzone después de procesar
|
||||
$this->dropzoneVisible = false;
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
$this->handleValidationException($e);
|
||||
|
||||
} catch (QueryException $e) {
|
||||
$this->handleDatabaseException($e);
|
||||
|
||||
} catch (ModelNotFoundException $e) {
|
||||
$this->handleException('danger', 'Registro no encontrado.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->handleException('danger', 'Error al procesar el archivo: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruta de la vista asociada con este formulario.
|
||||
*
|
||||
|
@ -40,7 +40,6 @@ class UserShow extends Component
|
||||
$is_prospect,
|
||||
$is_customer,
|
||||
$is_provider,
|
||||
$is_user,
|
||||
$status;
|
||||
public $deleteUserImage;
|
||||
public $cuentaUsuarioAlert,
|
||||
@ -55,7 +54,6 @@ class UserShow extends Component
|
||||
'is_prospect' => 'nullable|boolean',
|
||||
'is_customer' => 'nullable|boolean',
|
||||
'is_provider' => 'nullable|boolean',
|
||||
'is_user' => 'nullable|boolean',
|
||||
'pricelist_id' => 'nullable|integer',
|
||||
'enable_credit' => 'nullable|boolean',
|
||||
'credit_days' => 'nullable|integer',
|
||||
@ -102,7 +100,6 @@ class UserShow extends Component
|
||||
$this->is_prospect = $this->user->is_prospect? true : false;
|
||||
$this->is_customer = $this->user->is_customer? true : false;
|
||||
$this->is_provider = $this->user->is_provider? true : false;
|
||||
$this->is_user = $this->user->is_user? true : false;
|
||||
$this->pricelist_id = $this->user->pricelist_id;
|
||||
$this->enable_credit = $this->user->enable_credit? true : false;
|
||||
$this->credit_days = $this->user->credit_days;
|
||||
@ -140,7 +137,6 @@ class UserShow extends Component
|
||||
$validatedData['is_prospect'] = $validatedData['is_prospect'] ? 1 : 0;
|
||||
$validatedData['is_customer'] = $validatedData['is_customer'] ? 1 : 0;
|
||||
$validatedData['is_provider'] = $validatedData['is_provider'] ? 1 : 0;
|
||||
$validatedData['is_user'] = $validatedData['is_user'] ? 1 : 0;
|
||||
$validatedData['pricelist_id'] = $validatedData['pricelist_id'] ?: null;
|
||||
$validatedData['enable_credit'] = $validatedData['enable_credit'] ? 1 : 0;
|
||||
$validatedData['credit_days'] = $validatedData['credit_days'] ?: null;
|
||||
@ -150,7 +146,6 @@ class UserShow extends Component
|
||||
$validatedData['cargo'] = null;
|
||||
$validatedData['is_prospect'] = null;
|
||||
$validatedData['is_provider'] = null;
|
||||
$validatedData['is_user'] = null;
|
||||
$validatedData['enable_credit'] = null;
|
||||
$validatedData['credit_days'] = null;
|
||||
$validatedData['credit_limit'] = null;
|
||||
|
@ -6,11 +6,11 @@ use Koneko\VuexyAdmin\Models\User;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class UserCount extends Component
|
||||
class UsersCount extends Component
|
||||
{
|
||||
public $total, $enabled, $disabled;
|
||||
|
||||
protected $listeners = ['refreshUserCount' => 'updateCounts'];
|
||||
protected $listeners = ['refreshUsersCount' => 'updateCounts'];
|
||||
|
||||
public function mount()
|
||||
{
|
@ -2,17 +2,11 @@
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use Livewire\WithFileUploads;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
|
||||
|
||||
class UserIndex extends AbstractIndexComponent
|
||||
class UsersIndex extends AbstractIndexComponent
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $doc_file;
|
||||
public $dropzoneVisible = true;
|
||||
|
||||
/**
|
||||
* Almacena rutas útiles para la funcionalidad de edición o eliminación.
|
||||
*/
|
||||
@ -32,32 +26,14 @@ class UserIndex extends AbstractIndexComponent
|
||||
protected function columns(): array
|
||||
{
|
||||
return [
|
||||
'action' => 'Acciones',
|
||||
'code' => 'Código personal',
|
||||
'full_name' => 'Nombre Completo',
|
||||
'email' => 'Correo Electrónico',
|
||||
'parent_name' => 'Responsable',
|
||||
'parent_email' => 'Correo Responsable',
|
||||
'company' => 'Empresa',
|
||||
'birth_date' => 'Fecha de Nacimiento',
|
||||
'hire_date' => 'Fecha de Contratación',
|
||||
'curp' => 'CURP',
|
||||
'nss' => 'NSS',
|
||||
'job_title' => 'Puesto',
|
||||
'rfc' => 'RFC',
|
||||
'nombre_fiscal' => 'Nombre Fiscal',
|
||||
'profile_photo_path' => 'Foto de Perfil',
|
||||
'is_partner' => 'Socio',
|
||||
'is_employee' => 'Empleado',
|
||||
'is_prospect' => 'Prospecto',
|
||||
'is_customer' => 'Cliente',
|
||||
'is_provider' => 'Proveedor',
|
||||
'is_user' => 'Usuario',
|
||||
'status' => 'Estatus',
|
||||
'creator' => 'Creado Por',
|
||||
'creator_email' => 'Correo Creador',
|
||||
'created_at' => 'Fecha de Creación',
|
||||
'updated_at' => 'Última Modificación',
|
||||
'action' => 'Acciones',
|
||||
'full_name' => 'Nombre completo',
|
||||
'email' => 'Correo electrónico',
|
||||
'email_verified_at' => 'Correo verificado',
|
||||
'created_by' => 'Creado Por',
|
||||
'status' => 'Estatus',
|
||||
'created_at' => 'Fecha Creación',
|
||||
'updated_at' => 'Última Modificación',
|
||||
];
|
||||
}
|
||||
|
||||
@ -86,14 +62,27 @@ class UserIndex extends AbstractIndexComponent
|
||||
'formatter' => 'emailFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'parent_name' => [
|
||||
'formatter' => 'contactParentFormatter',
|
||||
'email_verified_at' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'agent_name' => [
|
||||
'formatter' => 'agentFormatter',
|
||||
'parent_id' => [
|
||||
'formatter' => 'parentProfileFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'agent_id' => [
|
||||
'formatter' => 'agentProfileFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'phone' => [
|
||||
'formatter' => 'telFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'mobile' => [
|
||||
'formatter' => 'telFormatter',
|
||||
],
|
||||
'whatsapp' => [
|
||||
'formatter' => 'whatsappFormatter',
|
||||
],
|
||||
'company' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
],
|
||||
@ -103,10 +92,16 @@ class UserIndex extends AbstractIndexComponent
|
||||
'nss' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'job_title' => [
|
||||
'license_number' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'job_position' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'pais' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'rfc' => [
|
||||
'visible' => false,
|
||||
],
|
||||
@ -114,6 +109,7 @@ class UserIndex extends AbstractIndexComponent
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
|
||||
'domicilio_fiscal' => [
|
||||
'visible' => false,
|
||||
],
|
||||
@ -176,14 +172,14 @@ class UserIndex extends AbstractIndexComponent
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'is_provider' => [
|
||||
'is_supplier' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'is_user' => [
|
||||
'is_carrier' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
@ -211,69 +207,6 @@ class UserIndex extends AbstractIndexComponent
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa el documento recibido (CFDI XML o Constancia PDF).
|
||||
*/
|
||||
public function processDocument()
|
||||
{
|
||||
// Verificamos si el archivo es válido
|
||||
if (!$this->doc_file instanceof UploadedFile) {
|
||||
return $this->addError('doc_file', 'No se pudo recibir el archivo.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Validar tipo de archivo
|
||||
$this->validate([
|
||||
'doc_file' => 'required|mimes:pdf,xml|max:2048'
|
||||
]);
|
||||
|
||||
|
||||
// **Detectar el tipo de documento**
|
||||
$extension = strtolower($this->doc_file->getClientOriginalExtension());
|
||||
|
||||
// **Procesar según el tipo de archivo**
|
||||
switch ($extension) {
|
||||
case 'xml':
|
||||
$service = new FacturaXmlService();
|
||||
$data = $service->processUploadedFile($this->doc_file);
|
||||
break;
|
||||
|
||||
case 'pdf':
|
||||
$service = new ConstanciaFiscalService();
|
||||
$data = $service->extractData($this->doc_file);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Formato de archivo no soportado.");
|
||||
}
|
||||
|
||||
dd($data);
|
||||
|
||||
// **Asignar los valores extraídos al formulario**
|
||||
$this->rfc = $data['rfc'] ?? null;
|
||||
$this->name = $data['name'] ?? null;
|
||||
$this->email = $data['email'] ?? null;
|
||||
$this->tel = $data['telefono'] ?? null;
|
||||
//$this->direccion = $data['domicilio_fiscal'] ?? null;
|
||||
|
||||
// Ocultar el Dropzone después de procesar
|
||||
$this->dropzoneVisible = false;
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
$this->handleValidationException($e);
|
||||
|
||||
} catch (QueryException $e) {
|
||||
$this->handleDatabaseException($e);
|
||||
|
||||
} catch (ModelNotFoundException $e) {
|
||||
$this->handleException('danger', 'Registro no encontrado.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->handleException('danger', 'Error al procesar el archivo: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Montamos el componente y llamamos al parent::mount() para configurar la tabla.
|
||||
*/
|
66
Livewire/VuexyAdmin/AppDescriptionSettings.php
Normal file
66
Livewire/VuexyAdmin/AppDescriptionSettings.php
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\SettingsService;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
|
||||
class AppDescriptionSettings extends Component
|
||||
{
|
||||
private $targetNotify = "#app-description-settings-card .notification-container";
|
||||
|
||||
public $app_name,
|
||||
$title,
|
||||
$description;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->resetForm();
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'app_name' => 'required|string|max:255',
|
||||
'title' => 'required|string|max:255',
|
||||
'description' => 'nullable|string|max:255',
|
||||
]);
|
||||
|
||||
// Guardar título del sitio en configuraciones
|
||||
$SettingsService = app(SettingsService::class);
|
||||
|
||||
$SettingsService->set('admin.app_name', $this->app_name, null, 'vuexy-admin');
|
||||
$SettingsService->set('admin.title', $this->title, null, 'vuexy-admin');
|
||||
$SettingsService->set('admin.description', $this->description, null, 'vuexy-admin');
|
||||
|
||||
// Limpiar cache de plantilla
|
||||
app(AdminTemplateService::class)->clearAdminVarsCache();
|
||||
|
||||
// Recargamos el formulario
|
||||
$this->resetForm();
|
||||
|
||||
// Notificación de éxito
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.'
|
||||
);
|
||||
}
|
||||
|
||||
public function resetForm()
|
||||
{
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = app(AdminTemplateService::class)->getAdminVars();
|
||||
|
||||
$this->app_name = $settings['app_name'];
|
||||
$this->title = $settings['title'];
|
||||
$this->description = $settings['description'];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.vuexy.app-description-settings');
|
||||
}
|
||||
}
|
@ -1,19 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Koneko\VuexyAdmin\Services\AdminSettingsService;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
|
||||
class GeneralSettings extends Component
|
||||
class AppFaviconSettings extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
private $targetNotify = "#general-settings-card .notification-container";
|
||||
private $targetNotify = "#app-favicon-settings-card .notification-container";
|
||||
|
||||
public $admin_title;
|
||||
public $admin_favicon_16x16,
|
||||
$admin_favicon_76x76,
|
||||
$admin_favicon_120x120,
|
||||
@ -25,50 +24,25 @@ class GeneralSettings extends Component
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
public function loadSettings($clearcache = false)
|
||||
{
|
||||
$this->upload_image_favicon = null;
|
||||
|
||||
$adminTemplateService = app(AdminTemplateService::class);
|
||||
|
||||
if ($clearcache) {
|
||||
$adminTemplateService->clearAdminVarsCache();
|
||||
}
|
||||
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = $adminTemplateService->getAdminVars();
|
||||
|
||||
$this->admin_title = $settings['title'];
|
||||
$this->admin_favicon_16x16 = $settings['favicon']['16x16'];
|
||||
$this->admin_favicon_76x76 = $settings['favicon']['76x76'];
|
||||
$this->admin_favicon_120x120 = $settings['favicon']['120x120'];
|
||||
$this->admin_favicon_152x152 = $settings['favicon']['152x152'];
|
||||
$this->admin_favicon_180x180 = $settings['favicon']['180x180'];
|
||||
$this->admin_favicon_192x192 = $settings['favicon']['192x192'];
|
||||
$this->resetForm();
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'admin_title' => 'required|string|max:255',
|
||||
'upload_image_favicon' => 'nullable|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||
'upload_image_favicon' => 'required|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||
]);
|
||||
|
||||
$adminSettingsService = app(AdminSettingsService::class);
|
||||
// Procesar favicon
|
||||
app(AdminSettingsService::class)->processAndSaveFavicon($this->upload_image_favicon);
|
||||
|
||||
// Guardar título del sitio en configuraciones
|
||||
$adminSettingsService->updateSetting('admin_title', $this->admin_title);
|
||||
// Limpiar cache de plantilla
|
||||
app(AdminTemplateService::class)->clearAdminVarsCache();
|
||||
|
||||
// Procesar favicon si se ha cargado una imagen
|
||||
if ($this->upload_image_favicon) {
|
||||
$adminSettingsService->processAndSaveFavicon($this->upload_image_favicon);
|
||||
}
|
||||
|
||||
$this->loadSettings(true);
|
||||
// Recargamos el formulario
|
||||
$this->resetForm();
|
||||
|
||||
// Notificación de éxito
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
@ -77,8 +51,22 @@ class GeneralSettings extends Component
|
||||
);
|
||||
}
|
||||
|
||||
public function resetForm()
|
||||
{
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = app(AdminTemplateService::class)->getAdminVars();
|
||||
|
||||
$this->upload_image_favicon = null;
|
||||
$this->admin_favicon_16x16 = $settings['favicon']['16x16'];
|
||||
$this->admin_favicon_76x76 = $settings['favicon']['76x76'];
|
||||
$this->admin_favicon_120x120 = $settings['favicon']['120x120'];
|
||||
$this->admin_favicon_152x152 = $settings['favicon']['152x152'];
|
||||
$this->admin_favicon_180x180 = $settings['favicon']['180x180'];
|
||||
$this->admin_favicon_192x192 = $settings['favicon']['192x192'];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.admin-settings.general-settings');
|
||||
return view('vuexy-admin::livewire.vuexy.app-favicon-settings');
|
||||
}
|
||||
}
|
110
Livewire/VuexyAdmin/GlobalSettingOffCanvasForm.php
Normal file
110
Livewire/VuexyAdmin/GlobalSettingOffCanvasForm.php
Normal file
@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormOffCanvasComponent;
|
||||
|
||||
/**
|
||||
* Class GlobalSettingOffCanvasForm
|
||||
*
|
||||
* Componente Livewire para gestionar parámetros globales del sistema.
|
||||
* Permite almacenar configuraciones personalizadas y valores múltiples tipos.
|
||||
*
|
||||
* @package Koneko\VuexyAdmin\Livewire\Settings
|
||||
*/
|
||||
class GlobalSettingOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
{
|
||||
public $id, $key, $category, $user_id,
|
||||
$value_string, $value_integer, $value_boolean,
|
||||
$value_float, $value_text;
|
||||
|
||||
public $confirmDeletion;
|
||||
|
||||
protected $casts = [
|
||||
'value_boolean' => 'boolean',
|
||||
'value_integer' => 'integer',
|
||||
'value_float' => 'float',
|
||||
];
|
||||
|
||||
protected $listeners = [
|
||||
'editGlobalSetting' => 'loadFormModel',
|
||||
'confirmDeletionGlobalSetting' => 'loadFormModelForDeletion',
|
||||
];
|
||||
|
||||
protected function model(): string
|
||||
{
|
||||
return Setting::class;
|
||||
}
|
||||
|
||||
protected function fields(): array
|
||||
{
|
||||
return [
|
||||
'key', 'category', 'user_id',
|
||||
'value_string', 'value_integer', 'value_boolean',
|
||||
'value_float', 'value_text'
|
||||
];
|
||||
}
|
||||
|
||||
protected function defaults(): array
|
||||
{
|
||||
return [
|
||||
'category' => 'general',
|
||||
];
|
||||
}
|
||||
|
||||
protected function focusOnOpen(): string
|
||||
{
|
||||
return 'key';
|
||||
}
|
||||
|
||||
protected function dynamicRules(string $mode): array
|
||||
{
|
||||
if ($mode === 'delete') {
|
||||
return ['confirmDeletion' => 'accepted'];
|
||||
}
|
||||
|
||||
$uniqueRule = Rule::unique('settings', 'key')
|
||||
->where(fn ($q) => $q
|
||||
->where('user_id', $this->user_id)
|
||||
->where('category', $this->category)
|
||||
);
|
||||
|
||||
if ($mode === 'edit') {
|
||||
$uniqueRule = $uniqueRule->ignore($this->id);
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => ['required', 'string', $uniqueRule],
|
||||
'category' => ['nullable', 'string', 'max:96'],
|
||||
'user_id' => ['nullable', 'integer', 'exists:users,id'],
|
||||
'value_string' => ['nullable', 'string', 'max:255'],
|
||||
'value_integer' => ['nullable', 'integer'],
|
||||
'value_boolean' => ['nullable', 'boolean'],
|
||||
'value_float' => ['nullable', 'numeric'],
|
||||
'value_text' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
|
||||
protected function attributes(): array
|
||||
{
|
||||
return [
|
||||
'key' => 'clave de configuración',
|
||||
'category' => 'categoría',
|
||||
];
|
||||
}
|
||||
|
||||
protected function messages(): array
|
||||
{
|
||||
return [
|
||||
'key.required' => 'La clave del parámetro es obligatoria.',
|
||||
'key.unique' => 'Ya existe una configuración con esta clave en esa categoría.',
|
||||
];
|
||||
}
|
||||
|
||||
protected function viewPath(): string
|
||||
{
|
||||
return 'vuexy-admin::livewire.global-settings.offcanvas-form';
|
||||
}
|
||||
}
|
103
Livewire/VuexyAdmin/GlobalSettingsIndex.php
Normal file
103
Livewire/VuexyAdmin/GlobalSettingsIndex.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;;
|
||||
|
||||
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
|
||||
/**
|
||||
* Listado de Configuraciones (settings), extiende la clase base AbstractIndexComponent
|
||||
* para reutilizar la lógica de configuración y renderizado de tablas.
|
||||
*/
|
||||
class GlobalSettingsIndex extends AbstractIndexComponent
|
||||
{
|
||||
/**
|
||||
* Define la clase o instancia del modelo a usar.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function model(): string
|
||||
{
|
||||
return Setting::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna las columnas (header) de la tabla.
|
||||
* Se eligen las columnas más relevantes para mantener una interfaz mobile-first.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function columns(): array
|
||||
{
|
||||
return [
|
||||
'action' => 'Acciones',
|
||||
'key' => 'Clave',
|
||||
'category' => 'Categoría',
|
||||
'user_fullname' => 'Usuario',
|
||||
'created_at' => 'Creado',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna el formato (formatter) para cada columna.
|
||||
* Se aplican formatters para resaltar la información y se establecen propiedades de alineación y visibilidad.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function format(): array
|
||||
{
|
||||
return [
|
||||
'action' => [
|
||||
'formatter' => 'settingActionFormatter',
|
||||
'onlyFormatter' => true,
|
||||
],
|
||||
'key' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBadgeFormatter',
|
||||
'params' => ['color' => 'primary'],
|
||||
],
|
||||
'align' => 'center',
|
||||
'switchable' => false,
|
||||
],
|
||||
'category' => [
|
||||
'switchable' => false,
|
||||
],
|
||||
'user_fullname' => [
|
||||
'switchable' => false,
|
||||
],
|
||||
'created_at' => [
|
||||
'formatter' => 'whitespaceNowrapFormatter',
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sobrescribe la configuración base de la tabla para ajustar
|
||||
* la vista y funcionalidades específicas del catálogo.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function bootstraptableConfig(): array
|
||||
{
|
||||
return array_merge(parent::bootstraptableConfig(), [
|
||||
'sortName' => 'key',
|
||||
'exportFileName' => 'Configuración',
|
||||
'showFullscreen' => false,
|
||||
'showPaginationSwitch' => false,
|
||||
'showRefresh' => false,
|
||||
'pagination' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna la vista a renderizar para este componente.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function viewPath(): string
|
||||
{
|
||||
return 'vuexy-admin::livewire.global-settings.index';
|
||||
}
|
||||
}
|
61
Livewire/VuexyAdmin/LogoOnDarkBgSettings.php
Normal file
61
Livewire/VuexyAdmin/LogoOnDarkBgSettings.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Koneko\VuexyAdmin\Services\AdminSettingsService;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
|
||||
class LogoOnDarkBgSettings extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
private $targetNotify = "#logo-on-dark-bg-settings-card .notification-container";
|
||||
|
||||
public $admin_image_logo_dark,
|
||||
$upload_image_logo_dark;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->resetForm();
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'upload_image_logo_dark' => 'required|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||
]);
|
||||
|
||||
// Procesar favicon si se ha cargado una imagen
|
||||
app(AdminSettingsService::class)->processAndSaveImageLogo($this->upload_image_logo_dark, 'dark');
|
||||
|
||||
// Limpiar cache de plantilla
|
||||
app(AdminTemplateService::class)->clearAdminVarsCache();
|
||||
|
||||
// Recargamos el formulario
|
||||
$this->resetForm();
|
||||
|
||||
// Notificación de éxito
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.'
|
||||
);
|
||||
}
|
||||
|
||||
public function resetForm()
|
||||
{
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = app(AdminTemplateService::class)->getAdminVars();
|
||||
|
||||
$this->upload_image_logo_dark = null;
|
||||
$this->admin_image_logo_dark = $settings['image_logo']['large_dark'];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.vuexy.logo-on-dark-bg-settings');
|
||||
}
|
||||
}
|
61
Livewire/VuexyAdmin/LogoOnLightBgSettings.php
Normal file
61
Livewire/VuexyAdmin/LogoOnLightBgSettings.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Koneko\VuexyAdmin\Services\AdminSettingsService;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
|
||||
class LogoOnLightBgSettings extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
private $targetNotify = "#logo-on-light-bg-settings-card .notification-container";
|
||||
|
||||
public $admin_image_logo,
|
||||
$upload_image_logo;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->resetForm();
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'upload_image_logo' => 'required|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||
]);
|
||||
|
||||
// Procesar favicon si se ha cargado una imagen
|
||||
app(AdminSettingsService::class)->processAndSaveImageLogo($this->upload_image_logo);
|
||||
|
||||
// Limpiar cache de plantilla
|
||||
app(AdminTemplateService::class)->clearAdminVarsCache();
|
||||
|
||||
// Recargamos el formulario
|
||||
$this->resetForm();
|
||||
|
||||
// Notificación de éxito
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.'
|
||||
);
|
||||
}
|
||||
|
||||
public function resetForm()
|
||||
{
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = app(AdminTemplateService::class)->getAdminVars();
|
||||
|
||||
$this->upload_image_logo = null;
|
||||
$this->admin_image_logo = $settings['image_logo']['large'];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.vuexy.logo-on-light-bg-settings');
|
||||
}
|
||||
}
|
87
Livewire/VuexyAdmin/QuickAccessWidget.php
Normal file
87
Livewire/VuexyAdmin/QuickAccessWidget.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
class QuickAccessWidget extends Component
|
||||
{
|
||||
public $quickAccessItems = [];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$menuConfig = config('vuexy_menu');
|
||||
$this->quickAccessItems = $this->processMenu($menuConfig);
|
||||
}
|
||||
|
||||
private function processMenu(array $menu): array
|
||||
{
|
||||
$user = Auth::user();
|
||||
$accessItems = [];
|
||||
|
||||
foreach ($menu as $section => $items) {
|
||||
if (!isset($items['submenu']) || !is_array($items['submenu'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$categoryData = [
|
||||
'title' => $section,
|
||||
'icon' => $items['icon'] ?? 'ti ti-folder',
|
||||
'description' => $items['description'] ?? '',
|
||||
'submenu' => []
|
||||
];
|
||||
|
||||
$this->processSubmenu($items['submenu'], $categoryData['submenu'], $user);
|
||||
|
||||
if (!empty($categoryData['submenu'])) {
|
||||
$accessItems[] = $categoryData;
|
||||
}
|
||||
}
|
||||
|
||||
return $accessItems;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function processSubmenu(array $submenu, array &$categorySubmenu, $user)
|
||||
{
|
||||
foreach ($submenu as $title => $item) {
|
||||
// Si el elemento NO tiene 'route' ni 'url' y SOLO contiene un submenu, no lo mostramos como acceso directo
|
||||
if (!isset($item['route']) && !isset($item['url']) && isset($item['submenu'])) {
|
||||
// Procesamos los submenús de este elemento sin agregarlo directamente a la lista
|
||||
$this->processSubmenu($item['submenu'], $categorySubmenu, $user);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Validar si el usuario tiene permiso
|
||||
$can = $item['can'] ?? null;
|
||||
if (!$can || $user->can($can)) {
|
||||
// Si tiene ruta y existe en Laravel, usarla; si no, usar url, y si tampoco hay, usar 'javascript:;'
|
||||
$routeExists = isset($item['route']) && Route::has($item['route']);
|
||||
$url = $routeExists ? route($item['route']) : ($item['url'] ?? 'javascript:;');
|
||||
|
||||
// Agregar elemento al submenu si tiene un destino válido
|
||||
$categorySubmenu[] = [
|
||||
'title' => $title,
|
||||
'icon' => $item['icon'] ?? 'ti ti-circle',
|
||||
'url' => $url,
|
||||
];
|
||||
}
|
||||
|
||||
// Si el elemento tiene un submenu, también lo procesamos
|
||||
if (isset($item['submenu']) && is_array($item['submenu'])) {
|
||||
$this->processSubmenu($item['submenu'], $categorySubmenu, $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.vuexy.quick-access-widget', [
|
||||
'quickAccessItems' => $this->quickAccessItems,
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
@ -11,9 +11,9 @@ use Symfony\Component\Mime\Email;
|
||||
use Koneko\VuexyAdmin\Services\GlobalSettingsService;
|
||||
|
||||
|
||||
class MailSmtpSettings extends Component
|
||||
class SendmailSettings extends Component
|
||||
{
|
||||
private $targetNotify = "#mail-smtp-settings-card .notification-container";
|
||||
private $targetNotify = "#sendmail-settings-card .notification-container";
|
||||
|
||||
public $change_smtp_settings,
|
||||
$host,
|
||||
@ -68,10 +68,7 @@ class MailSmtpSettings extends Component
|
||||
|
||||
public function loadSettings()
|
||||
{
|
||||
$globalSettingsService = app(GlobalSettingsService::class);
|
||||
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = $globalSettingsService->getMailSystemConfig();
|
||||
$settings = app(GlobalSettingsService::class)->getMailSystemConfig();
|
||||
|
||||
$this->change_smtp_settings = false;
|
||||
$this->save_button_disabled = true;
|
||||
@ -170,6 +167,6 @@ class MailSmtpSettings extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.admin-settings.mail-smtp-settings');
|
||||
return view('vuexy-admin::livewire.vuexy.sendmail-settings');
|
||||
}
|
||||
}
|
121
Livewire/VuexyAdmin/VuexyInterfaceSettings.php
Normal file
121
Livewire/VuexyAdmin/VuexyInterfaceSettings.php
Normal file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
use Koneko\VuexyAdmin\Services\GlobalSettingsService;
|
||||
use Koneko\VuexyAdmin\Services\SettingsService;
|
||||
|
||||
class VuexyInterfaceSettings extends Component
|
||||
{
|
||||
private $targetNotify = "#interface-settings-card .notification-container";
|
||||
|
||||
public $uniqueId;
|
||||
|
||||
public $vuexy_myLayout,
|
||||
$vuexy_myTheme,
|
||||
$vuexy_myStyle,
|
||||
$vuexy_hasCustomizer,
|
||||
$vuexy_displayCustomizer,
|
||||
$vuexy_contentLayout,
|
||||
$vuexy_navbarType,
|
||||
$vuexy_footerFixed,
|
||||
$vuexy_menuFixed,
|
||||
$vuexy_menuCollapsed,
|
||||
$vuexy_headerType,
|
||||
$vuexy_showDropdownOnHover,
|
||||
$vuexy_authViewMode,
|
||||
$vuexy_maxQuickLinks;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->uniqueId = uniqid();
|
||||
|
||||
$this->resetForm();
|
||||
}
|
||||
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'vuexy_maxQuickLinks' => 'required|integer|min:2|max:20',
|
||||
]);
|
||||
|
||||
// Guardar configuraciones utilizando SettingsService
|
||||
$SettingsService = app(SettingsService::class);
|
||||
|
||||
$SettingsService->set('config.vuexy.custom.myLayout', $this->vuexy_myLayout, null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.myTheme', $this->vuexy_myTheme, null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.myStyle', $this->vuexy_myStyle, null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.hasCustomizer', $this->vuexy_hasCustomizer, null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.displayCustomizer', ($this->vuexy_hasCustomizer? $this->vuexy_displayCustomizer: false), null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.contentLayout', $this->vuexy_contentLayout, null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.navbarType', ($this->vuexy_myLayout == 'vertical' ? $this->vuexy_navbarType: null), null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.footerFixed', $this->vuexy_footerFixed, null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.menuFixed', ($this->vuexy_myLayout == 'vertical' ? $this->vuexy_menuFixed: null), null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.menuCollapsed', ($this->vuexy_myLayout == 'vertical' ? $this->vuexy_menuCollapsed: null), null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.headerType', ($this->vuexy_myLayout == 'horizontal' ? $this->vuexy_headerType: null), null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.showDropdownOnHover', ($this->vuexy_myLayout == 'horizontal' ? $this->vuexy_showDropdownOnHover: null), null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.authViewMode', $this->vuexy_authViewMode, null, 'vuexy-admin');
|
||||
$SettingsService->set('config.vuexy.custom.maxQuickLinks', $this->vuexy_maxQuickLinks, null, 'vuexy-admin');
|
||||
|
||||
// Elimina la Cache de Configuraciones
|
||||
app(GlobalSettingsService::class)->clearSystemConfigCache();
|
||||
|
||||
// Refrescar el componente actual
|
||||
$this->dispatch('clearLocalStoregeTemplateCustomizer');
|
||||
|
||||
// Notificación de éxito
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.',
|
||||
deferReload: true
|
||||
);
|
||||
}
|
||||
|
||||
public function clearCustomConfig()
|
||||
{
|
||||
// Elimina las claves config.vuexy.* para cargar los valores por defecto
|
||||
app(GlobalSettingsService::class)->clearVuexyConfig();
|
||||
|
||||
// Refrescar el componente actual
|
||||
$this->dispatch('clearLocalStoregeTemplateCustomizer');
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.',
|
||||
deferReload: true
|
||||
);
|
||||
}
|
||||
|
||||
public function resetForm()
|
||||
{
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = app(AdminTemplateService::class)->getVuexyCustomizerVars();
|
||||
|
||||
$this->vuexy_myLayout = $settings['myLayout'];
|
||||
$this->vuexy_myTheme = $settings['myTheme'];
|
||||
$this->vuexy_myStyle = $settings['myStyle'];
|
||||
$this->vuexy_hasCustomizer = $settings['hasCustomizer'];
|
||||
$this->vuexy_displayCustomizer = $settings['displayCustomizer'];
|
||||
$this->vuexy_contentLayout = $settings['contentLayout'];
|
||||
$this->vuexy_navbarType = $settings['navbarType'];
|
||||
$this->vuexy_footerFixed = $settings['footerFixed'];
|
||||
$this->vuexy_menuFixed = $settings['menuFixed'];
|
||||
$this->vuexy_menuCollapsed = $settings['menuCollapsed'];
|
||||
$this->vuexy_headerType = $settings['headerType'];
|
||||
$this->vuexy_showDropdownOnHover = $settings['showDropdownOnHover'];
|
||||
$this->vuexy_authViewMode = $settings['authViewMode'];
|
||||
$this->vuexy_maxQuickLinks = $settings['maxQuickLinks'];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.vuexy.interface-settings');
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MediaItem extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'url',
|
||||
'imageable_type',
|
||||
'imageable_id',
|
||||
'type',
|
||||
'sub_type',
|
||||
'url',
|
||||
'path',
|
||||
'title',
|
||||
'description',
|
||||
'order',
|
||||
];
|
||||
|
||||
// the list of types values that can be stored in table
|
||||
const TYPE_CARD = 1;
|
||||
const TYPE_BANNER = 2;
|
||||
const TYPE_COVER = 3;
|
||||
const TYPE_GALLERY = 4;
|
||||
const TYPE_BANNER_HOME = 5;
|
||||
const TYPE_CARD2 = 6;
|
||||
const TYPE_BANNER2 = 7;
|
||||
const TYPE_COVER2 = 8;
|
||||
|
||||
/**
|
||||
* List of names for each types.
|
||||
* @var array
|
||||
*/
|
||||
public static $typesList = [
|
||||
self::TYPE_CARD => 'Card',
|
||||
self::TYPE_BANNER => 'Banner',
|
||||
self::TYPE_COVER => 'Cover',
|
||||
self::TYPE_GALLERY => 'Gallery',
|
||||
self::TYPE_BANNER_HOME => 'Banner Home',
|
||||
self::TYPE_CARD2 => 'Card 2',
|
||||
self::TYPE_BANNER2 => 'Banner 2',
|
||||
self::TYPE_COVER2 => 'Cover 2',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the parent imageable model (user or post).
|
||||
*/
|
||||
public function imageable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
}
|
@ -3,37 +3,91 @@
|
||||
namespace Koneko\VuexyAdmin\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Setting extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
use HasFactory;
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Configuración del modelo
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
protected $table = 'settings';
|
||||
|
||||
protected $fillable = [
|
||||
'key',
|
||||
'value',
|
||||
'category',
|
||||
'user_id',
|
||||
'value_string',
|
||||
'value_integer',
|
||||
'value_boolean',
|
||||
'value_float',
|
||||
'value_text',
|
||||
'value_binary',
|
||||
'mime_type',
|
||||
'file_name',
|
||||
'updated_by',
|
||||
];
|
||||
|
||||
public $timestamps = false;
|
||||
protected $casts = [
|
||||
'user_id' => 'integer',
|
||||
'value_integer' => 'integer',
|
||||
'value_boolean' => 'boolean',
|
||||
'value_float' => 'float',
|
||||
'updated_by' => 'integer',
|
||||
];
|
||||
|
||||
// Relación con el usuario
|
||||
public function user()
|
||||
// ─────────────────────────────────────────────
|
||||
// Metadatos personalizados para el generador de componentes
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
public string $tagName = 'setting';
|
||||
public string $columnNameLabel = 'key';
|
||||
public string $singularName = 'Configuración';
|
||||
public string $pluralName = 'Configuraciones';
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Relaciones
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
// Scope para obtener configuraciones de un usuario específico
|
||||
public function scopeForUser($query, $userId)
|
||||
public function updatedBy(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'updated_by');
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Scopes
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Configuraciones para un usuario específico.
|
||||
*/
|
||||
public function scopeForUser($query, int $userId)
|
||||
{
|
||||
return $query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
// Configuraciones globales (sin usuario)
|
||||
/**
|
||||
* Configuraciones globales (sin usuario).
|
||||
*/
|
||||
public function scopeGlobal($query)
|
||||
{
|
||||
return $query->whereNull('user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Incluir columna virtual `value` en la consulta.
|
||||
*/
|
||||
public function scopeWithVirtualValue($query)
|
||||
{
|
||||
return $query->select(['key', 'value']);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,377 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Models;
|
||||
|
||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Intervention\Image\Typography\FontFactory;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
|
||||
use OwenIt\Auditing\Auditable;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
use Koneko\VuexyAdmin\Notifications\CustomResetPasswordNotification;
|
||||
|
||||
if (trait_exists(\Koneko\VuexyContacts\Traits\HasContactsAttributes::class)) {
|
||||
trait DynamicContactsAttributes {
|
||||
use \Koneko\VuexyContacts\Traits\HasContactsAttributes;
|
||||
}
|
||||
} else {
|
||||
trait DynamicContactsAttributes {}
|
||||
}
|
||||
|
||||
class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
||||
{
|
||||
use HasRoles,
|
||||
HasApiTokens,
|
||||
HasFactory,
|
||||
Notifiable,
|
||||
TwoFactorAuthenticatable,
|
||||
Auditable,
|
||||
DynamicContactsAttributes;
|
||||
|
||||
// the list of status values that can be stored in table
|
||||
const STATUS_ENABLED = 10;
|
||||
const STATUS_DISABLED = 1;
|
||||
const STATUS_REMOVED = 0;
|
||||
|
||||
const AVATAR_DISK = 'public';
|
||||
const PROFILE_PHOTO_DIR = 'profile-photos';
|
||||
const INITIAL_AVATAR_DIR = 'initial-avatars';
|
||||
const INITIAL_MAX_LENGTH = 4;
|
||||
|
||||
const AVATAR_WIDTH = 512;
|
||||
const AVATAR_HEIGHT = 512;
|
||||
const AVATAR_BACKGROUND = '#EBF4FF'; // Fondo por defecto
|
||||
const AVATAR_COLORS = [
|
||||
'#7367f0',
|
||||
'#808390',
|
||||
'#28c76f',
|
||||
'#ff4c51',
|
||||
'#ff9f43',
|
||||
'#00bad1',
|
||||
'#4b4b4b',
|
||||
];
|
||||
|
||||
/**
|
||||
* List of names for each status.
|
||||
* @var array
|
||||
*/
|
||||
public static $statusList = [
|
||||
self::STATUS_ENABLED => 'Habilitado',
|
||||
self::STATUS_DISABLED => 'Deshabilitado',
|
||||
self::STATUS_REMOVED => 'Eliminado',
|
||||
];
|
||||
|
||||
/**
|
||||
* List of names for each status.
|
||||
* @var array
|
||||
*/
|
||||
public static $statusListClass = [
|
||||
self::STATUS_ENABLED => 'success',
|
||||
self::STATUS_DISABLED => 'warning',
|
||||
self::STATUS_REMOVED => 'danger',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'last_name',
|
||||
'email',
|
||||
'password',
|
||||
'profile_photo_path',
|
||||
'status',
|
||||
'created_by',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'two_factor_recovery_codes',
|
||||
'two_factor_secret',
|
||||
];
|
||||
|
||||
/**
|
||||
* The accessors to append to the model's array form.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $appends = [
|
||||
'profile_photo_url',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attributes to include in the Audit.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $auditInclude = [
|
||||
'name',
|
||||
'email',
|
||||
];
|
||||
|
||||
public function updateProfilePhoto(UploadedFile $image_avatar)
|
||||
{
|
||||
try {
|
||||
// Verificar si el archivo existe
|
||||
if (!file_exists($image_avatar->getRealPath()))
|
||||
throw new \Exception('El archivo no existe en la ruta especificada.');
|
||||
|
||||
if (!in_array($image_avatar->getClientOriginalExtension(), ['jpg', 'jpeg', 'png']))
|
||||
throw new \Exception('El formato del archivo debe ser JPG o PNG.');
|
||||
|
||||
// Directorio donde se guardarán los avatares
|
||||
$avatarDisk = self::AVATAR_DISK;
|
||||
$avatarPath = self::PROFILE_PHOTO_DIR;
|
||||
$avatarName = uniqid('avatar_') . '.png'; // Nombre único para el avatar
|
||||
|
||||
// Crear la instancia de ImageManager
|
||||
$driver = config('image.driver', 'gd');
|
||||
$manager = new ImageManager($driver);
|
||||
|
||||
// Crear el directorio si no existe
|
||||
if (!Storage::disk($avatarDisk)->exists($avatarPath))
|
||||
Storage::disk($avatarDisk)->makeDirectory($avatarPath);
|
||||
|
||||
// Leer la imagen
|
||||
$image = $manager->read($image_avatar->getRealPath());
|
||||
|
||||
// crop the best fitting 5:3 (600x360) ratio and resize to 600x360 pixel
|
||||
$image->cover(self::AVATAR_WIDTH, self::AVATAR_HEIGHT);
|
||||
|
||||
// Guardar la imagen en el disco de almacenamiento gestionado por Laravel
|
||||
Storage::disk($avatarDisk)->put($avatarPath . '/' . $avatarName, $image->toPng(indexed: true));
|
||||
|
||||
// Elimina el avatar existente si hay uno
|
||||
$this->deleteProfilePhoto();
|
||||
|
||||
// Update the user's profile photo path
|
||||
$this->forceFill([
|
||||
'profile_photo_path' => $avatarName,
|
||||
])->save();
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Ocurrió un error al actualizar el avatar. ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteProfilePhoto()
|
||||
{
|
||||
if (!empty($this->profile_photo_path)) {
|
||||
$avatarDisk = self::AVATAR_DISK;
|
||||
|
||||
Storage::disk($avatarDisk)->delete($this->profile_photo_path);
|
||||
|
||||
$this->forceFill([
|
||||
'profile_photo_path' => null,
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
public function getAvatarColor()
|
||||
{
|
||||
// Selecciona un color basado en el id del usuario
|
||||
return self::AVATAR_COLORS[$this->id % count(self::AVATAR_COLORS)];
|
||||
}
|
||||
|
||||
public static function getAvatarImage($name, $color, $background, $size)
|
||||
{
|
||||
$avatarDisk = self::AVATAR_DISK;
|
||||
$directory = self::INITIAL_AVATAR_DIR;
|
||||
$initials = self::getInitials($name);
|
||||
|
||||
$cacheKey = "avatar-{$initials}-{$color}-{$background}-{$size}";
|
||||
$path = "{$directory}/{$cacheKey}.png";
|
||||
$storagePath = storage_path("app/public/{$path}");
|
||||
|
||||
// Verificar si el avatar ya está en caché
|
||||
if (Storage::disk($avatarDisk)->exists($path))
|
||||
return response()->file($storagePath);
|
||||
|
||||
// Crear el avatar
|
||||
$image = self::createAvatarImage($name, $color, $background, $size);
|
||||
|
||||
// Guardar en el directorio de iniciales
|
||||
Storage::disk($avatarDisk)->put($path, $image->toPng(indexed: true));
|
||||
|
||||
// Retornar la imagen directamente
|
||||
return response()->file($storagePath);
|
||||
}
|
||||
|
||||
private static function createAvatarImage($name, $color, $background, $size)
|
||||
{
|
||||
// Usar la configuración del driver de imagen
|
||||
$driver = config('image.driver', 'gd');
|
||||
$manager = new ImageManager($driver);
|
||||
|
||||
$initials = self::getInitials($name);
|
||||
|
||||
// Obtener la ruta correcta de la fuente dentro del paquete
|
||||
$fontPath = __DIR__ . '/../storage/fonts/OpenSans-Bold.ttf';
|
||||
|
||||
// Crear la imagen con fondo
|
||||
$image = $manager->create($size, $size)
|
||||
->fill($background);
|
||||
|
||||
// Escribir texto en la imagen
|
||||
$image->text(
|
||||
$initials,
|
||||
$size / 2, // Centrar horizontalmente
|
||||
$size / 2, // Centrar verticalmente
|
||||
function (FontFactory $font) use ($color, $size, $fontPath) {
|
||||
$font->file($fontPath);
|
||||
$font->size($size * 0.4);
|
||||
$font->color($color);
|
||||
$font->align('center');
|
||||
$font->valign('middle');
|
||||
}
|
||||
);
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
public static function getInitials($name)
|
||||
{
|
||||
// Manejar casos de nombres vacíos o nulos
|
||||
if (empty($name))
|
||||
return 'NA';
|
||||
|
||||
// Usar array_map para mayor eficiencia
|
||||
$initials = implode('', array_map(function ($word) {
|
||||
return mb_substr($word, 0, 1);
|
||||
}, explode(' ', $name)));
|
||||
|
||||
$initials = substr($initials, 0, self::INITIAL_MAX_LENGTH);
|
||||
|
||||
return strtoupper($initials);
|
||||
}
|
||||
|
||||
public function getProfilePhotoUrlAttribute()
|
||||
{
|
||||
if ($this->profile_photo_path)
|
||||
return Storage::url(self::PROFILE_PHOTO_DIR . '/' . $this->profile_photo_path);
|
||||
|
||||
// Generar URL del avatar por iniciales
|
||||
$name = urlencode($this->fullname);
|
||||
$color = ltrim($this->getAvatarColor(), '#');
|
||||
$background = ltrim(self::AVATAR_BACKGROUND, '#');
|
||||
$size = (self::AVATAR_WIDTH + self::AVATAR_HEIGHT) / 2;
|
||||
|
||||
return url("/admin/usuario/avatar?name={$name}&color={$color}&background={$background}&size={$size}");
|
||||
}
|
||||
|
||||
public function getFullnameAttribute()
|
||||
{
|
||||
return trim($this->name . ' ' . $this->last_name);
|
||||
}
|
||||
|
||||
public function getInitialsAttribute()
|
||||
{
|
||||
return self::getInitials($this->fullname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía la notificación de restablecimiento de contraseña.
|
||||
*
|
||||
* @param string $token
|
||||
*/
|
||||
public function sendPasswordResetNotification($token)
|
||||
{
|
||||
// Usar la notificación personalizada
|
||||
$this->notify(new CustomResetPasswordNotification($token));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtener usuarios activos con una excepción para incluir un usuario específico desactivado.
|
||||
*
|
||||
* @param array $filters Filtros opcionales como ['type' => 'user', 'status' => 1]
|
||||
* @param int|null $includeUserId ID de usuario específico a incluir aunque esté inactivo
|
||||
* @return array
|
||||
*/
|
||||
public static function getUsersListWithInactive(int $includeUserId = null, array $filters = []): array
|
||||
{
|
||||
$query = self::query();
|
||||
|
||||
// Filtro por tipo de usuario
|
||||
if (isset($filters['type'])) {
|
||||
switch ($filters['type']) {
|
||||
case 'partner':
|
||||
$query->where('is_partner', 1);
|
||||
break;
|
||||
case 'employee':
|
||||
$query->where('is_employee', 1);
|
||||
break;
|
||||
case 'prospect':
|
||||
$query->where('is_prospect', 1);
|
||||
break;
|
||||
case 'customer':
|
||||
$query->where('is_customer', 1);
|
||||
break;
|
||||
case 'provider':
|
||||
$query->where('is_provider', 1);
|
||||
break;
|
||||
case 'user':
|
||||
$query->where('is_user', 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Incluir usuarios activos o el usuario desactivado seleccionado
|
||||
$query->where(function ($q) use ($filters, $includeUserId) {
|
||||
if (isset($filters['status'])) {
|
||||
$q->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
if ($includeUserId) {
|
||||
$q->orWhere('id', $includeUserId);
|
||||
}
|
||||
});
|
||||
|
||||
// Formatear los datos como id => "Nombre Apellido"
|
||||
return $query->pluck(\DB::raw("CONCAT(name, ' ', IFNULL(last_name, ''))"), 'id')->toArray();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Relations
|
||||
*/
|
||||
|
||||
// User who created this user
|
||||
public function creator()
|
||||
{
|
||||
return $this->belongsTo(self::class, 'created_by');
|
||||
}
|
||||
|
||||
public function isActive()
|
||||
{
|
||||
return $this->status === self::STATUS_ENABLED;
|
||||
}
|
||||
|
||||
}
|
102
Models/User.php
102
Models/User.php
@ -23,23 +23,7 @@ class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
||||
const STATUS_DISABLED = 1;
|
||||
const STATUS_REMOVED = 0;
|
||||
|
||||
const AVATAR_DISK = 'public';
|
||||
const PROFILE_PHOTO_DIR = 'profile-photos';
|
||||
const INITIAL_AVATAR_DIR = 'initial-avatars';
|
||||
const INITIAL_MAX_LENGTH = 4;
|
||||
|
||||
const AVATAR_WIDTH = 512;
|
||||
const AVATAR_HEIGHT = 512;
|
||||
const AVATAR_BACKGROUND = '#EBF4FF'; // Fondo por defecto
|
||||
const AVATAR_COLORS = [
|
||||
'#7367f0',
|
||||
'#808390',
|
||||
'#28c76f',
|
||||
'#ff4c51',
|
||||
'#ff9f43',
|
||||
'#00bad1',
|
||||
'#4b4b4b',
|
||||
];
|
||||
const INITIAL_MAX_LENGTH = 3;
|
||||
|
||||
/**
|
||||
* List of names for each status.
|
||||
@ -148,6 +132,50 @@ class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
||||
'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the URL for the user's profile photo.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProfilePhotoUrlAttribute()
|
||||
{
|
||||
if ($this->profile_photo_path) {
|
||||
return asset('storage/profile-photos/' . $this->profile_photo_path);
|
||||
}
|
||||
|
||||
return $this->defaultProfilePhotoUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default profile photo URL if no profile photo has been uploaded.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function defaultProfilePhotoUrl()
|
||||
{
|
||||
return route('admin.core.user-profile.avatar', ['name' => $this->fullname]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcula las iniciales a partir del nombre.
|
||||
*
|
||||
* @param string $name Nombre completo.
|
||||
*
|
||||
* @return string Iniciales en mayúsculas.
|
||||
*/
|
||||
public static function getInitials($name)
|
||||
{
|
||||
if (empty($name)) {
|
||||
return 'NA';
|
||||
}
|
||||
|
||||
$initials = implode('', array_map(function ($word) {
|
||||
return mb_substr($word, 0, 1);
|
||||
}, explode(' ', $name)));
|
||||
|
||||
return strtoupper(substr($initials, 0, self::INITIAL_MAX_LENGTH));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full name of the user.
|
||||
*
|
||||
@ -179,45 +207,6 @@ class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
||||
$this->notify(new CustomResetPasswordNotification($token));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener usuarios activos con una excepción para incluir un usuario específico desactivado.
|
||||
*
|
||||
* @param array $filters Filtros opcionales como ['type' => 'user', 'status' => 1]
|
||||
* @param int|null $includeUserId ID de usuario específico a incluir aunque esté inactivo
|
||||
* @return array
|
||||
*/
|
||||
public static function getUsersListWithInactive($includeUserId = null, array $filters = []): array
|
||||
{
|
||||
$query = self::query();
|
||||
|
||||
// Filtro por tipo de usuario dinámico
|
||||
$tipoUsuarios = [
|
||||
'partner' => 'is_partner',
|
||||
'employee' => 'is_employee',
|
||||
'prospect' => 'is_prospect',
|
||||
'customer' => 'is_customer',
|
||||
'provider' => 'is_provider',
|
||||
'user' => 'is_user',
|
||||
];
|
||||
|
||||
if (isset($filters['type']) && isset($tipoUsuarios[$filters['type']])) {
|
||||
$query->where($tipoUsuarios[$filters['type']], 1);
|
||||
}
|
||||
|
||||
// Filtrar por estado o incluir usuario inactivo
|
||||
$query->where(function ($q) use ($filters, $includeUserId) {
|
||||
if (isset($filters['status'])) {
|
||||
$q->where('status', $filters['status']);
|
||||
}
|
||||
|
||||
if ($includeUserId) {
|
||||
$q->orWhere('id', $includeUserId);
|
||||
}
|
||||
});
|
||||
|
||||
return $query->pluck(\DB::raw("CONCAT(name, ' ', IFNULL(last_name, ''))"), 'id')->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* User who created this user
|
||||
*/
|
||||
@ -233,5 +222,4 @@ class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
||||
{
|
||||
return $this->status === self::STATUS_ENABLED;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class CustomResetPasswordNotification extends Notification
|
||||
'email' => $notifiable->getEmailForPasswordReset()
|
||||
], false));
|
||||
|
||||
$appTitle = Setting::global()->where('key', 'website_title')->first()->value ?? Config::get('koneko.appTitle');
|
||||
$appTitle = Setting::withVirtualValue()->where('key', 'website_title')->first()->value ?? Config::get('koneko.appTitle');
|
||||
$imageBase64 = 'data:image/png;base64,' . base64_encode(file_get_contents(public_path('/assets/img/logo/koneko-04.png')));
|
||||
$expireMinutes = Config::get('auth.passwords.' . Config::get('auth.defaults.passwords') . '.expire', 60);
|
||||
|
||||
@ -90,6 +90,7 @@ class CustomResetPasswordNotification extends Notification
|
||||
{
|
||||
try {
|
||||
$smtpConfig = Setting::where('key', 'LIKE', 'mail_%')
|
||||
->withVirtualValue()
|
||||
->pluck('value', 'key');
|
||||
|
||||
if ($smtpConfig->isEmpty()) {
|
||||
|
@ -21,10 +21,6 @@ class ConfigServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
// Cargar configuración del sistema
|
||||
$globalSettingsService = app(GlobalSettingsService::class);
|
||||
$globalSettingsService->loadSystemConfig();
|
||||
|
||||
// Cargar configuración del sistema a través del servicio
|
||||
app(GlobalSettingsService::class)->loadSystemConfig();
|
||||
}
|
||||
|
@ -2,20 +2,28 @@
|
||||
|
||||
namespace Koneko\VuexyAdmin\Providers;
|
||||
|
||||
use Koneko\VuexyAdmin\Http\Middleware\AdminTemplateMiddleware;
|
||||
use Koneko\VuexyAdmin\Listeners\{ClearUserCache,HandleUserLogin};
|
||||
use Koneko\VuexyAdmin\Livewire\Users\{UserIndex,UserShow,UserForm,UserOffCanvasForm};
|
||||
use Koneko\VuexyAdmin\Livewire\Roles\RoleIndex;
|
||||
use Koneko\VuexyAdmin\Livewire\Permissions\PermissionIndex;
|
||||
use Koneko\VuexyAdmin\Livewire\Cache\{CacheFunctions,CacheStats,SessionStats,MemcachedStats,RedisStats};
|
||||
use Koneko\VuexyAdmin\Livewire\AdminSettings\{ApplicationSettings,GeneralSettings,InterfaceSettings,MailSmtpSettings,MailSenderResponseSettings};
|
||||
use Koneko\VuexyAdmin\Console\Commands\CleanInitialAvatars;
|
||||
use Koneko\VuexyAdmin\Helpers\VuexyHelper;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyAdmin\Http\Middleware\AdminTemplateMiddleware;
|
||||
use Illuminate\Auth\Events\{Login,Logout};
|
||||
use Illuminate\Foundation\AliasLoader;
|
||||
use Illuminate\Support\Facades\{URL,Event,Blade};
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use Illuminate\Foundation\AliasLoader;
|
||||
use Illuminate\Auth\Events\{Login,Logout};
|
||||
use Koneko\VuexyAdmin\Listeners\{ClearUserCache,HandleUserLogin};
|
||||
|
||||
use Koneko\VuexyAdmin\Livewire\Cache\{CacheFunctions,CacheStats,SessionStats,MemcachedStats,RedisStats};
|
||||
use Koneko\VuexyAdmin\Livewire\Permissions\{PermissionsIndex,PermissionOffCanvasForm};
|
||||
use Koneko\VuexyAdmin\Livewire\Profile\{UpdateProfileInformationForm,UpdatePasswordForm,TwoFactorAuthenticationForm,LogoutOtherBrowser,DeleteUserForm};
|
||||
use Koneko\VuexyAdmin\Livewire\Roles\{RolesIndex,RoleCards};
|
||||
use Koneko\VuexyAdmin\Livewire\Users\{UsersIndex,UsersCount,UserForm,UserOffCanvasForm};
|
||||
|
||||
use Koneko\VuexyAdmin\Livewire\VuexyAdmin\{LogoOnLightBgSettings,LogoOnDarkBgSettings,AppDescriptionSettings,AppFaviconSettings};
|
||||
use Koneko\VuexyAdmin\Livewire\VuexyAdmin\SendmailSettings;
|
||||
use Koneko\VuexyAdmin\Livewire\VuexyAdmin\{VuexyInterfaceSettings};
|
||||
use Koneko\VuexyAdmin\Livewire\VuexyAdmin\{GlobalSettingsIndex,GlobalSettingOffCanvasForm};
|
||||
use Koneko\VuexyAdmin\Livewire\VuexyAdmin\QuickAccessWidget;
|
||||
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Livewire\Livewire;
|
||||
use OwenIt\Auditing\AuditableObserver;
|
||||
use Spatie\Permission\PermissionServiceProvider;
|
||||
@ -48,14 +56,17 @@ class VuexyAdminServiceProvider extends ServiceProvider
|
||||
URL::forceScheme('https');
|
||||
}
|
||||
|
||||
|
||||
// Registrar alias del middleware
|
||||
$this->app['router']->aliasMiddleware('admin', AdminTemplateMiddleware::class);
|
||||
|
||||
|
||||
// Sobrescribir ruta de traducciones para asegurar que se usen las del paquete
|
||||
$this->app->bind('path.lang', function () {
|
||||
return __DIR__ . '/../resources/lang';
|
||||
});
|
||||
|
||||
|
||||
// Register the module's routes
|
||||
$this->loadRoutesFrom(__DIR__.'/../routes/admin.php');
|
||||
|
||||
@ -63,6 +74,7 @@ class VuexyAdminServiceProvider extends ServiceProvider
|
||||
// Cargar vistas del paquete
|
||||
$this->loadViewsFrom(__DIR__.'/../resources/views', 'vuexy-admin');
|
||||
|
||||
|
||||
// Registrar Componentes Blade
|
||||
Blade::componentNamespace('VuexyAdmin\\View\\Components', 'vuexy-admin');
|
||||
|
||||
@ -100,32 +112,60 @@ class VuexyAdminServiceProvider extends ServiceProvider
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
// Registrar Livewire Components
|
||||
$components = [
|
||||
'user-index' => UserIndex::class,
|
||||
'user-show' => UserShow::class,
|
||||
'user-form' => UserForm::class,
|
||||
'user-offcanvas-form' => UserOffCanvasForm::class,
|
||||
'role-index' => RoleIndex::class,
|
||||
'permission-index' => PermissionIndex::class,
|
||||
// Usuarios
|
||||
'vuexy-admin::users-index' => UsersIndex::class,
|
||||
'vuexy-admin::users-count' => UsersCount::class,
|
||||
'vuexy-admin::user-form' => UserForm::class,
|
||||
'vuexy-admin::user-offcanvas-form' => UserOffCanvasForm::class,
|
||||
|
||||
// Perfil del usuario
|
||||
'vuexy-admin::update-profile-information-form' => UpdateProfileInformationForm::class,
|
||||
'vuexy-admin::update-password-form' => UpdatePasswordForm::class,
|
||||
'vuexy-admin::two-factor-authentication' => TwoFactorAuthenticationForm::class,
|
||||
'vuexy-admin::logout-other-browser' => LogoutOtherBrowser::class,
|
||||
'vuexy-admin::delete-user-form' => DeleteUserForm::class,
|
||||
|
||||
'general-settings' => GeneralSettings::class,
|
||||
'application-settings' => ApplicationSettings::class,
|
||||
'interface-settings' => InterfaceSettings::class,
|
||||
'mail-smtp-settings' => MailSmtpSettings::class,
|
||||
'mail-sender-response-settings' => MailSenderResponseSettings::class,
|
||||
'cache-stats' => CacheStats::class,
|
||||
'session-stats' => SessionStats::class,
|
||||
'redis-stats' => RedisStats::class,
|
||||
'memcached-stats' => MemcachedStats::class,
|
||||
'cache-functions' => CacheFunctions::class,
|
||||
// Roles y Permisos
|
||||
'vuexy-admin::roles-index' => RolesIndex::class,
|
||||
'vuexy-admin::role-cards' => RoleCards::class,
|
||||
'vuexy-admin::permissions-index' => PermissionsIndex::class,
|
||||
'vuexy-admin::permission-offcanvas-form' => PermissionOffCanvasForm::class,
|
||||
|
||||
// Identidad de aplicación
|
||||
'vuexy-admin::app-description-settings' => AppDescriptionSettings::class,
|
||||
'vuexy-admin::app-favicon-settings' => AppFaviconSettings::class,
|
||||
'vuexy-admin::logo-on-light-bg-settings' => LogoOnLightBgSettings::class,
|
||||
'vuexy-admin::logo-on-dark-bg-settings' => LogoOnDarkBgSettings::class,
|
||||
|
||||
// Ajustes de interfaz
|
||||
'vuexy-admin::interface-settings' => VuexyInterfaceSettings::class,
|
||||
|
||||
// Cache
|
||||
'vuexy-admin::cache-stats' => CacheStats::class,
|
||||
'vuexy-admin::session-stats' => SessionStats::class,
|
||||
'vuexy-admin::redis-stats' => RedisStats::class,
|
||||
'vuexy-admin::memcached-stats' => MemcachedStats::class,
|
||||
'vuexy-admin::cache-functions' => CacheFunctions::class,
|
||||
|
||||
// Configuración de correo saliente
|
||||
'vuexy-admin::sendmail-settings' => SendmailSettings::class,
|
||||
|
||||
// Configuraciones globales
|
||||
'vuexy-admin::global-settings-index' => GlobalSettingsIndex::class,
|
||||
'vuexy-admin::global-setting-offcanvas-form' => GlobalSettingOffCanvasForm::class,
|
||||
|
||||
// Accesos rápidos de la barra de menú
|
||||
'vuexy-admin::quick-access-widget' => QuickAccessWidget::class,
|
||||
];
|
||||
|
||||
foreach ($components as $alias => $component) {
|
||||
Livewire::component($alias, $component);
|
||||
}
|
||||
|
||||
|
||||
// Registrar auditoría en usuarios
|
||||
User::observe(AuditableObserver::class);
|
||||
}
|
||||
|
@ -27,19 +27,24 @@ abstract class BootstrapTableQueryBuilder
|
||||
foreach ($this->config['joins'] as $join) {
|
||||
$type = $join['type'] ?? 'join';
|
||||
|
||||
$this->query->{$type}($join['table'], function($joinObj) use ($join) {
|
||||
$joinObj->on($join['first'], '=', $join['second']);
|
||||
// Soporte para alias
|
||||
$table = $join['table'];
|
||||
$alias = $join['alias'] ?? null;
|
||||
$tableWithAlias = $alias ? DB::raw("{$table} as {$alias}") : $table;
|
||||
|
||||
// Soporte para AND en ON, si está definidio
|
||||
$this->query->{$type}($tableWithAlias, function ($joinObj) use ($join, $alias) {
|
||||
$first = $join['first'];
|
||||
$second = $join['second'];
|
||||
|
||||
$joinObj->on($first, '=', $second);
|
||||
|
||||
// Soporte para condiciones adicionales tipo AND
|
||||
if (!empty($join['and'])) {
|
||||
foreach ((array) $join['and'] as $andCondition) {
|
||||
// 'sat_codigo_postal.c_estado = sat_localidad.c_estado'
|
||||
$parts = explode('=', $andCondition);
|
||||
|
||||
if (count($parts) === 2) {
|
||||
$left = trim($parts[0]);
|
||||
$right = trim($parts[1]);
|
||||
|
||||
$joinObj->whereRaw("$left = $right");
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,29 @@ use Illuminate\Support\Facades\Storage;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
|
||||
/**
|
||||
* Servicio para gestionar la configuración administrativa de VuexyAdmin
|
||||
*
|
||||
* Este servicio maneja el procesamiento y almacenamiento de imágenes del favicon
|
||||
* y logos del panel administrativo, incluyendo diferentes versiones y tamaños.
|
||||
*
|
||||
* @package Koneko\VuexyAdmin\Services
|
||||
*/
|
||||
class AdminSettingsService
|
||||
{
|
||||
/** @var string Driver de procesamiento de imágenes */
|
||||
private $driver;
|
||||
private $imageDisk = 'public';
|
||||
private $favicon_basePath = 'favicon/';
|
||||
|
||||
/** @var string Disco de almacenamiento para imágenes */
|
||||
private $imageDisk = 'public';
|
||||
|
||||
/** @var string Ruta base para favicons */
|
||||
private $favicon_basePath = 'favicon/';
|
||||
|
||||
/** @var string Ruta base para logos */
|
||||
private $image_logo_basePath = 'images/logo/';
|
||||
|
||||
/** @var array<string,array<int>> Tamaños predefinidos para favicons */
|
||||
private $faviconsSizes = [
|
||||
'180x180' => [180, 180],
|
||||
'192x192' => [192, 192],
|
||||
@ -22,28 +38,40 @@ class AdminSettingsService
|
||||
'16x16' => [16, 16],
|
||||
];
|
||||
|
||||
private $imageLogoMaxPixels1 = 22500; // Primera versión (px^2)
|
||||
private $imageLogoMaxPixels2 = 75625; // Segunda versión (px^2)
|
||||
private $imageLogoMaxPixels3 = 262144; // Tercera versión (px^2)
|
||||
private $imageLogoMaxPixels4 = 230400; // Tercera versión (px^2) en Base64
|
||||
/** @var int Área máxima en píxeles para la primera versión del logo */
|
||||
private $imageLogoMaxPixels1 = 22500;
|
||||
|
||||
protected $cacheTTL = 60 * 24 * 30; // 30 días en minutos
|
||||
/** @var int Área máxima en píxeles para la segunda versión del logo */
|
||||
private $imageLogoMaxPixels2 = 75625;
|
||||
|
||||
/** @var int Área máxima en píxeles para la tercera versión del logo */
|
||||
private $imageLogoMaxPixels3 = 262144;
|
||||
|
||||
/** @var int Área máxima en píxeles para la versión base64 del logo */
|
||||
private $imageLogoMaxPixels4 = 230400;
|
||||
|
||||
/** @var int Tiempo de vida en caché en minutos */
|
||||
protected $cacheTTL = 60 * 24 * 30;
|
||||
|
||||
/**
|
||||
* Constructor del servicio
|
||||
*
|
||||
* Inicializa el driver de procesamiento de imágenes desde la configuración
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->driver = config('image.driver', 'gd');
|
||||
}
|
||||
|
||||
public function updateSetting(string $key, string $value): bool
|
||||
{
|
||||
$setting = Setting::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => trim($value)]
|
||||
);
|
||||
|
||||
return $setting->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa y guarda un nuevo favicon
|
||||
*
|
||||
* Genera múltiples versiones del favicon en diferentes tamaños predefinidos,
|
||||
* elimina las versiones anteriores y actualiza la configuración.
|
||||
*
|
||||
* @param \Illuminate\Http\UploadedFile $image Archivo de imagen subido
|
||||
* @return void
|
||||
*/
|
||||
public function processAndSaveFavicon($image): void
|
||||
{
|
||||
Storage::makeDirectory($this->imageDisk . '/' . $this->favicon_basePath);
|
||||
@ -66,13 +94,20 @@ class AdminSettingsService
|
||||
Storage::disk($this->imageDisk)->put($resizedPath, $image->toPng(indexed: true));
|
||||
}
|
||||
|
||||
$this->updateSetting('admin_favicon_ns', $this->favicon_basePath . $imageName);
|
||||
// Actualizar configuración utilizando SettingService
|
||||
$SettingsService = app(SettingsService::class);
|
||||
$SettingsService->set('admin.favicon_ns', $this->favicon_basePath . $imageName, null, 'vuexy-admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina los favicons antiguos del almacenamiento
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteOldFavicons(): void
|
||||
{
|
||||
// Obtener el favicon actual desde la base de datos
|
||||
$currentFavicon = Setting::where('key', 'admin_favicon_ns')->value('value');
|
||||
$currentFavicon = Setting::where('key', 'admin.favicon_ns')->value('value');
|
||||
|
||||
if ($currentFavicon) {
|
||||
$filePaths = [
|
||||
@ -93,6 +128,16 @@ class AdminSettingsService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa y guarda un nuevo logo
|
||||
*
|
||||
* Genera múltiples versiones del logo con diferentes tamaños máximos,
|
||||
* incluyendo una versión en base64, y actualiza la configuración.
|
||||
*
|
||||
* @param \Illuminate\Http\UploadedFile $image Archivo de imagen subido
|
||||
* @param string $type Tipo de logo ('dark' para modo oscuro, '' para normal)
|
||||
* @return void
|
||||
*/
|
||||
public function processAndSaveImageLogo($image, string $type = ''): void
|
||||
{
|
||||
// Crear directorio si no existe
|
||||
@ -112,6 +157,15 @@ class AdminSettingsService
|
||||
$this->generateAndSaveImageAsBase64($image, $type, $this->imageLogoMaxPixels4); // Versión 3
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera y guarda una versión del logo
|
||||
*
|
||||
* @param \Intervention\Image\Interfaces\ImageInterface $image Imagen a procesar
|
||||
* @param string $type Tipo de logo ('dark' para modo oscuro, '' para normal)
|
||||
* @param int $maxPixels Área máxima en píxeles
|
||||
* @param string $suffix Sufijo para el nombre del archivo
|
||||
* @return void
|
||||
*/
|
||||
private function generateAndSaveImage($image, string $type, int $maxPixels, string $suffix = ''): void
|
||||
{
|
||||
$imageClone = clone $image;
|
||||
@ -120,6 +174,7 @@ class AdminSettingsService
|
||||
$this->resizeImageToMaxPixels($imageClone, $maxPixels);
|
||||
|
||||
$imageName = 'admin_image_logo' . ($suffix ? '_' . $suffix : '') . ($type == 'dark' ? '_dark' : '');
|
||||
$keyValue = 'admin.image.logo' . ($suffix ? '_' . $suffix : '') . ($type == 'dark' ? '_dark' : '');
|
||||
|
||||
// Generar nombre y ruta
|
||||
$imageNameUid = uniqid($imageName . '_', ".png");
|
||||
@ -129,9 +184,17 @@ class AdminSettingsService
|
||||
Storage::disk($this->imageDisk)->put($resizedPath, $imageClone->toPng(indexed: true));
|
||||
|
||||
// Actualizar configuración
|
||||
$this->updateSetting($imageName, $resizedPath);
|
||||
$SettingsService = app(SettingsService::class);
|
||||
$SettingsService->set($keyValue, $resizedPath, null, 'vuexy-admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Redimensiona una imagen manteniendo su proporción
|
||||
*
|
||||
* @param \Intervention\Image\Interfaces\ImageInterface $image Imagen a redimensionar
|
||||
* @param int $maxPixels Área máxima en píxeles
|
||||
* @return \Intervention\Image\Interfaces\ImageInterface
|
||||
*/
|
||||
private function resizeImageToMaxPixels($image, int $maxPixels)
|
||||
{
|
||||
// Obtener dimensiones originales de la imagen
|
||||
@ -163,7 +226,14 @@ class AdminSettingsService
|
||||
return $image;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Genera y guarda una versión del logo en formato base64
|
||||
*
|
||||
* @param \Intervention\Image\Interfaces\ImageInterface $image Imagen a procesar
|
||||
* @param string $type Tipo de logo ('dark' para modo oscuro, '' para normal)
|
||||
* @param int $maxPixels Área máxima en píxeles
|
||||
* @return void
|
||||
*/
|
||||
private function generateAndSaveImageAsBase64($image, string $type, int $maxPixels): void
|
||||
{
|
||||
$imageClone = clone $image;
|
||||
@ -175,12 +245,16 @@ class AdminSettingsService
|
||||
$base64Image = (string) $imageClone->toJpg(40)->toDataUri();
|
||||
|
||||
// Guardar como configuración
|
||||
$this->updateSetting(
|
||||
"admin_image_logo_base64" . ($type === 'dark' ? '_dark' : ''),
|
||||
$base64Image // Ya incluye "data:image/png;base64,"
|
||||
);
|
||||
$SettingsService = app(SettingsService::class);
|
||||
$SettingsService->set("admin.image.logo_base64" . ($type === 'dark' ? '_dark' : ''), $base64Image, null, 'vuexy-admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina las imágenes antiguas del logo
|
||||
*
|
||||
* @param string $type Tipo de logo ('dark' para modo oscuro, '' para normal)
|
||||
* @return void
|
||||
*/
|
||||
protected function deleteOldImageWebapp(string $type = ''): void
|
||||
{
|
||||
// Determinar prefijo según el tipo (normal o dark)
|
||||
@ -188,9 +262,9 @@ class AdminSettingsService
|
||||
|
||||
// Claves relacionadas con las imágenes que queremos limpiar
|
||||
$imageKeys = [
|
||||
"admin_image_logo{$suffix}",
|
||||
"admin_image_logo_small{$suffix}",
|
||||
"admin_image_logo_medium{$suffix}",
|
||||
"admin.image_logo{$suffix}",
|
||||
"admin.image_logo_small{$suffix}",
|
||||
"admin.image_logo_medium{$suffix}",
|
||||
];
|
||||
|
||||
// Recuperar las imágenes actuales en una sola consulta
|
||||
|
@ -7,48 +7,57 @@ use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
|
||||
/**
|
||||
* Servicio para gestionar la configuración y personalización del template administrativo.
|
||||
*
|
||||
* Esta clase maneja las configuraciones del template VuexyAdmin, incluyendo variables
|
||||
* de personalización, logos, favicons y otras configuraciones de la interfaz.
|
||||
* Implementa un sistema de caché para optimizar el rendimiento.
|
||||
*/
|
||||
class AdminTemplateService
|
||||
{
|
||||
protected $cacheTTL = 60 * 24 * 30; // 30 días en minutos
|
||||
/** @var int Tiempo de vida del caché en minutos (60 * 24 * 30 = 30 días) */
|
||||
protected $cacheTTL = 60 * 24 * 30;
|
||||
|
||||
public function updateSetting(string $key, string $value): bool
|
||||
{
|
||||
$setting = Setting::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => trim($value)]
|
||||
);
|
||||
|
||||
return $setting->save();
|
||||
}
|
||||
|
||||
public function getAdminVars($adminSetting = false): array
|
||||
/**
|
||||
* Obtiene las variables de configuración del admin.
|
||||
*
|
||||
* @param string $setting Clave específica de configuración a obtener
|
||||
* @return array Configuraciones del admin o valor específico si se proporciona $setting
|
||||
*/
|
||||
public function getAdminVars(string $setting = ''): array
|
||||
{
|
||||
try {
|
||||
// Verificar si el sistema está inicializado (la tabla `migrations` existe)
|
||||
if (!Schema::hasTable('migrations')) {
|
||||
return $this->getDefaultAdminVars($adminSetting);
|
||||
return $this->getDefaultAdminVars($setting);
|
||||
}
|
||||
|
||||
// Cargar desde el caché o la base de datos si está disponible
|
||||
return Cache::remember('admin_settings', $this->cacheTTL, function () use ($adminSetting) {
|
||||
$settings = Setting::global()
|
||||
->where('key', 'LIKE', 'admin_%')
|
||||
$adminVars = Cache::remember('admin_settings', $this->cacheTTL, function () {
|
||||
$settings = Setting::withVirtualValue()
|
||||
->where('key', 'LIKE', 'admin.%')
|
||||
->pluck('value', 'key')
|
||||
->toArray();
|
||||
|
||||
$adminSettings = $this->buildAdminVarsArray($settings);
|
||||
|
||||
return $adminSetting
|
||||
? $adminSettings[$adminSetting]
|
||||
: $adminSettings;
|
||||
return $this->buildAdminVarsArray($settings);
|
||||
});
|
||||
|
||||
return $setting ? ($adminVars[$setting] ?? []) : $adminVars;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// En caso de error, devolver valores predeterminados
|
||||
return $this->getDefaultAdminVars($adminSetting);
|
||||
return $this->getDefaultAdminVars($setting);
|
||||
}
|
||||
}
|
||||
|
||||
private function getDefaultAdminVars($adminSetting = false): array
|
||||
/**
|
||||
* Obtiene las variables predeterminadas del admin.
|
||||
*
|
||||
* @param string $setting Clave específica de configuración a obtener
|
||||
* @return array Configuraciones predeterminadas o valor específico si se proporciona $setting
|
||||
*/
|
||||
private function getDefaultAdminVars(string $setting = ''): array
|
||||
{
|
||||
$defaultSettings = [
|
||||
'title' => config('koneko.appTitle', 'Default Title'),
|
||||
@ -59,27 +68,41 @@ class AdminTemplateService
|
||||
'image_logo' => $this->getImageLogoPaths([]),
|
||||
];
|
||||
|
||||
return $adminSetting
|
||||
? $defaultSettings[$adminSetting] ?? null
|
||||
return $setting
|
||||
? $defaultSettings[$setting] ?? null
|
||||
: $defaultSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye el array de variables del admin a partir de las configuraciones.
|
||||
*
|
||||
* @param array $settings Array asociativo de configuraciones
|
||||
* @return array Array estructurado con las variables del admin
|
||||
*/
|
||||
private function buildAdminVarsArray(array $settings): array
|
||||
{
|
||||
return [
|
||||
'title' => $settings['admin_title'] ?? config('koneko.appTitle'),
|
||||
'title' => $settings['admin.title'] ?? config('koneko.appTitle'),
|
||||
'author' => config('koneko.author'),
|
||||
'description' => config('koneko.description'),
|
||||
'description' => $settings['admin.description'] ?? config('koneko.description'),
|
||||
'favicon' => $this->getFaviconPaths($settings),
|
||||
'app_name' => $settings['admin_app_name'] ?? config('koneko.appName'),
|
||||
'app_name' => $settings['admin.app_name'] ?? config('koneko.appName'),
|
||||
'image_logo' => $this->getImageLogoPaths($settings),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene las variables de personalización de Vuexy.
|
||||
*
|
||||
* Combina las configuraciones predeterminadas con las almacenadas en la base de datos,
|
||||
* aplicando las transformaciones necesarias para tipos específicos como booleanos.
|
||||
*
|
||||
* @return array Array asociativo con las variables de personalización
|
||||
*/
|
||||
public function getVuexyCustomizerVars()
|
||||
{
|
||||
// Obtener valores de la base de datos
|
||||
$settings = Setting::global()
|
||||
$settings = Setting::withVirtualValue()
|
||||
->where('key', 'LIKE', 'vuexy_%')
|
||||
->pluck('value', 'key')
|
||||
->toArray();
|
||||
@ -96,7 +119,7 @@ class AdminTemplateService
|
||||
$value = $settings[$vuexyKey] ?? $defaultValue;
|
||||
|
||||
// Forzar booleanos para claves específicas
|
||||
if (in_array($key, ['displayCustomizer', 'footerFixed', 'menuFixed', 'menuCollapsed', 'showDropdownOnHover'])) {
|
||||
if (in_array($key, ['hasCustomizer', 'displayCustomizer', 'footerFixed', 'menuFixed', 'menuCollapsed', 'showDropdownOnHover'])) {
|
||||
$value = filter_var($value, FILTER_VALIDATE_BOOLEAN);
|
||||
}
|
||||
|
||||
@ -106,12 +129,15 @@ class AdminTemplateService
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene los paths de favicon en distintos tamaños.
|
||||
* Genera las rutas para los diferentes tamaños de favicon.
|
||||
*
|
||||
* @param array $settings Array asociativo de configuraciones
|
||||
* @return array Array con las rutas de los favicons en diferentes tamaños
|
||||
*/
|
||||
private function getFaviconPaths(array $settings): array
|
||||
{
|
||||
$defaultFavicon = config('koneko.appFavicon');
|
||||
$namespace = $settings['admin_favicon_ns'] ?? null;
|
||||
$namespace = $settings['admin.favicon_ns'] ?? null;
|
||||
|
||||
return [
|
||||
'namespace' => $namespace,
|
||||
@ -125,30 +151,43 @@ class AdminTemplateService
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene los paths de los logos en distintos tamaños.
|
||||
* Genera las rutas para los diferentes tamaños y versiones del logo.
|
||||
*
|
||||
* @param array $settings Array asociativo de configuraciones
|
||||
* @return array Array con las rutas de los logos en diferentes tamaños y modos
|
||||
*/
|
||||
private function getImageLogoPaths(array $settings): array
|
||||
{
|
||||
$defaultLogo = config('koneko.appLogo');
|
||||
|
||||
return [
|
||||
'small' => $this->getImagePath($settings, 'admin_image_logo_small', $defaultLogo),
|
||||
'medium' => $this->getImagePath($settings, 'admin_image_logo_medium', $defaultLogo),
|
||||
'large' => $this->getImagePath($settings, 'admin_image_logo', $defaultLogo),
|
||||
'small_dark' => $this->getImagePath($settings, 'admin_image_logo_small_dark', $defaultLogo),
|
||||
'medium_dark' => $this->getImagePath($settings, 'admin_image_logo_medium_dark', $defaultLogo),
|
||||
'large_dark' => $this->getImagePath($settings, 'admin_image_logo_dark', $defaultLogo),
|
||||
'small' => $this->getImagePath($settings, 'admin.image.logo_small', $defaultLogo),
|
||||
'medium' => $this->getImagePath($settings, 'admin.image.logo_medium', $defaultLogo),
|
||||
'large' => $this->getImagePath($settings, 'admin.image.logo', $defaultLogo),
|
||||
'small_dark' => $this->getImagePath($settings, 'admin.image.logo_small_dark', $defaultLogo),
|
||||
'medium_dark' => $this->getImagePath($settings, 'admin.image.logo_medium_dark', $defaultLogo),
|
||||
'large_dark' => $this->getImagePath($settings, 'admin.image.logo_dark', $defaultLogo),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene un path de imagen o retorna un valor predeterminado.
|
||||
* Obtiene la ruta de una imagen específica desde las configuraciones.
|
||||
*
|
||||
* @param array $settings Array asociativo de configuraciones
|
||||
* @param string $key Clave de la configuración
|
||||
* @param string $default Valor predeterminado si no se encuentra la configuración
|
||||
* @return string Ruta de la imagen
|
||||
*/
|
||||
private function getImagePath(array $settings, string $key, string $default): string
|
||||
{
|
||||
return $settings[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia el caché de las variables del admin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearAdminVarsCache()
|
||||
{
|
||||
Cache::forget("admin_settings");
|
||||
|
@ -15,7 +15,7 @@ class AvatarInitialsService
|
||||
protected const INITIAL_MAX_LENGTH = 3;
|
||||
protected const AVATAR_BACKGROUND = '#EBF4FF';
|
||||
protected const AVATAR_COLORS = [
|
||||
'#7367f0',
|
||||
'#3b82f6',
|
||||
'#808390',
|
||||
'#28c76f',
|
||||
'#ff4c51',
|
||||
|
@ -6,8 +6,20 @@ use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
/**
|
||||
* Servicio para gestionar y obtener información de configuración del sistema de caché.
|
||||
*
|
||||
* Esta clase proporciona métodos para obtener información detallada sobre las configuraciones
|
||||
* de caché, sesión, base de datos y drivers del sistema. Permite consultar versiones,
|
||||
* estados y configuraciones de diferentes servicios como Redis, Memcached y bases de datos.
|
||||
*/
|
||||
class CacheConfigService
|
||||
{
|
||||
/**
|
||||
* Obtiene la configuración completa del sistema de caché y servicios relacionados.
|
||||
*
|
||||
* @return array Configuración completa que incluye caché, sesión, base de datos y drivers
|
||||
*/
|
||||
public function getConfig(): array
|
||||
{
|
||||
return [
|
||||
@ -20,7 +32,11 @@ class CacheConfigService
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtiene la configuración específica del sistema de caché.
|
||||
*
|
||||
* @return array Configuración del caché incluyendo driver, host y base de datos
|
||||
*/
|
||||
private function getCacheConfig(): array
|
||||
{
|
||||
$cacheConfig = Config::get('cache');
|
||||
@ -59,6 +75,11 @@ class CacheConfigService
|
||||
return $cacheConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la configuración del sistema de sesiones.
|
||||
*
|
||||
* @return array Configuración de sesiones incluyendo driver, host y base de datos
|
||||
*/
|
||||
private function getSessionConfig(): array
|
||||
{
|
||||
$sessionConfig = Config::get('session');
|
||||
@ -97,6 +118,11 @@ class CacheConfigService
|
||||
return $sessionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la configuración de la base de datos principal.
|
||||
*
|
||||
* @return array Configuración de la base de datos incluyendo host y nombre de la base de datos
|
||||
*/
|
||||
private function getDatabaseConfig(): array
|
||||
{
|
||||
$databaseConfig = Config::get('database');
|
||||
@ -109,7 +135,14 @@ class CacheConfigService
|
||||
return $databaseConfig;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtiene información sobre las versiones de los drivers en uso.
|
||||
*
|
||||
* Recopila información detallada sobre las versiones de los drivers de base de datos,
|
||||
* Redis y Memcached si están en uso en el sistema.
|
||||
*
|
||||
* @return array Información de versiones de los drivers activos
|
||||
*/
|
||||
private function getDriverVersion(): array
|
||||
{
|
||||
$drivers = [];
|
||||
@ -163,6 +196,11 @@ class CacheConfigService
|
||||
return $drivers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la versión del servidor MySQL.
|
||||
*
|
||||
* @return string Versión del servidor MySQL o mensaje de error
|
||||
*/
|
||||
private function getMySqlVersion(): string
|
||||
{
|
||||
try {
|
||||
@ -173,6 +211,11 @@ class CacheConfigService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la versión del servidor PostgreSQL.
|
||||
*
|
||||
* @return string Versión del servidor PostgreSQL o mensaje de error
|
||||
*/
|
||||
private function getPgSqlVersion(): string
|
||||
{
|
||||
try {
|
||||
@ -183,6 +226,11 @@ class CacheConfigService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la versión del servidor SQL Server.
|
||||
*
|
||||
* @return string Versión del servidor SQL Server o mensaje de error
|
||||
*/
|
||||
private function getSqlSrvVersion(): string
|
||||
{
|
||||
try {
|
||||
@ -193,6 +241,11 @@ class CacheConfigService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la versión del servidor Memcached.
|
||||
*
|
||||
* @return string Versión del servidor Memcached o mensaje de error
|
||||
*/
|
||||
private function getMemcachedVersion(): string
|
||||
{
|
||||
try {
|
||||
@ -213,6 +266,11 @@ class CacheConfigService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene la versión del servidor Redis.
|
||||
*
|
||||
* @return string Versión del servidor Redis o mensaje de error
|
||||
*/
|
||||
private function getRedisVersion(): string
|
||||
{
|
||||
try {
|
||||
@ -223,7 +281,14 @@ class CacheConfigService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifica si un driver específico está en uso en el sistema.
|
||||
*
|
||||
* Comprueba si el driver está siendo utilizado en caché, sesiones o colas.
|
||||
*
|
||||
* @param string $driver Nombre del driver a verificar
|
||||
* @return bool True si el driver está en uso, false en caso contrario
|
||||
*/
|
||||
protected function isDriverInUse(string $driver): bool
|
||||
{
|
||||
return in_array($driver, [
|
||||
|
@ -7,19 +7,38 @@ use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
/**
|
||||
* Servicio para gestionar y administrar el sistema de caché.
|
||||
*
|
||||
* Esta clase proporciona funcionalidades para administrar diferentes drivers de caché
|
||||
* (Redis, Memcached, Database, File), incluyendo operaciones como obtener estadísticas,
|
||||
* limpiar la caché y monitorear el uso de recursos.
|
||||
*/
|
||||
class CacheManagerService
|
||||
{
|
||||
/** @var string Driver de caché actualmente seleccionado */
|
||||
private string $driver;
|
||||
|
||||
public function __construct(string $driver = null)
|
||||
/**
|
||||
* Constructor del servicio de gestión de caché.
|
||||
*
|
||||
* @param mixed $driver Driver de caché a utilizar. Si es null, se usa el driver predeterminado
|
||||
*/
|
||||
public function __construct(mixed $driver = null)
|
||||
{
|
||||
$this->driver = $driver ?? config('cache.default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas de caché para el driver especificado.
|
||||
*
|
||||
* Recopila información detallada sobre el uso y rendimiento del sistema de caché,
|
||||
* incluyendo uso de memoria, número de elementos y estadísticas específicas del driver.
|
||||
*
|
||||
* @param mixed $driver Driver de caché del cual obtener estadísticas
|
||||
* @return array Estadísticas del sistema de caché
|
||||
*/
|
||||
public function getCacheStats(string $driver = null): array
|
||||
public function getCacheStats(mixed $driver = null): array
|
||||
{
|
||||
$driver = $driver ?? $this->driver;
|
||||
|
||||
@ -40,7 +59,13 @@ class CacheManagerService
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache(string $driver = null): array
|
||||
/**
|
||||
* Limpia la caché del driver especificado.
|
||||
*
|
||||
* @param mixed $driver Driver de caché a limpiar
|
||||
* @return array Resultado de la operación de limpieza
|
||||
*/
|
||||
public function clearCache(mixed $driver = null): array
|
||||
{
|
||||
$driver = $driver ?? $this->driver;
|
||||
|
||||
@ -88,6 +113,11 @@ class CacheManagerService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas detalladas del servidor Redis.
|
||||
*
|
||||
* @return array Información detallada del servidor Redis incluyendo versión, memoria, clientes y más
|
||||
*/
|
||||
public function getRedisStats()
|
||||
{
|
||||
try {
|
||||
@ -132,6 +162,11 @@ class CacheManagerService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas detalladas del servidor Memcached.
|
||||
*
|
||||
* @return array Información detallada del servidor Memcached incluyendo versión, memoria y estadísticas de uso
|
||||
*/
|
||||
public function getMemcachedStats()
|
||||
{
|
||||
try {
|
||||
@ -176,9 +211,10 @@ class CacheManagerService
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas para caché en base de datos.
|
||||
*
|
||||
* @return array Estadísticas de la caché en base de datos incluyendo cantidad de registros y uso de memoria
|
||||
*/
|
||||
private function _getDatabaseStats(): array
|
||||
{
|
||||
@ -196,6 +232,8 @@ class CacheManagerService
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas para caché en archivos.
|
||||
*
|
||||
* @return array Estadísticas de la caché en archivos incluyendo cantidad de archivos y uso de memoria
|
||||
*/
|
||||
private function _getFilecacheStats(): array
|
||||
{
|
||||
@ -211,6 +249,11 @@ class CacheManagerService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas específicas de Redis para la caché.
|
||||
*
|
||||
* @return array Estadísticas de Redis incluyendo cantidad de claves y uso de memoria
|
||||
*/
|
||||
private function _getRedisStats()
|
||||
{
|
||||
try {
|
||||
@ -227,6 +270,11 @@ class CacheManagerService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene estadísticas específicas de Memcached para la caché.
|
||||
*
|
||||
* @return array Estadísticas de Memcached incluyendo cantidad de elementos y uso de memoria
|
||||
*/
|
||||
public function _getMemcachedStats(): array
|
||||
{
|
||||
try {
|
||||
@ -254,6 +302,14 @@ class CacheManagerService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene información sobre las bases de datos Redis en uso.
|
||||
*
|
||||
* Analiza y recopila información sobre las diferentes bases de datos Redis
|
||||
* configuradas en el sistema (default, cache, sessions).
|
||||
*
|
||||
* @return array Información detallada de las bases de datos Redis
|
||||
*/
|
||||
private function getRedisDatabases(): array
|
||||
{
|
||||
// Verificar si Redis está en uso
|
||||
@ -300,7 +356,11 @@ class CacheManagerService
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Limpia la caché almacenada en base de datos.
|
||||
*
|
||||
* @return bool True si se eliminaron registros, False si no había registros para eliminar
|
||||
*/
|
||||
private function clearDatabaseCache(): bool
|
||||
{
|
||||
$count = DB::table(config('cache.stores.database.table'))->count();
|
||||
@ -313,6 +373,11 @@ class CacheManagerService
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia la caché almacenada en archivos.
|
||||
*
|
||||
* @return bool True si se eliminaron archivos, False si no había archivos para eliminar
|
||||
*/
|
||||
private function clearFilecache(): bool
|
||||
{
|
||||
$cachePath = config('cache.stores.file.path');
|
||||
@ -326,6 +391,11 @@ class CacheManagerService
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia la caché almacenada en Redis.
|
||||
*
|
||||
* @return bool True si se eliminaron claves, False si no había claves para eliminar
|
||||
*/
|
||||
private function clearRedisCache(): bool
|
||||
{
|
||||
$prefix = config('cache.prefix', '');
|
||||
@ -343,6 +413,11 @@ class CacheManagerService
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia la caché almacenada en Memcached.
|
||||
*
|
||||
* @return bool True si se limpió la caché, False en caso contrario
|
||||
*/
|
||||
private function clearMemcachedCache(): bool
|
||||
{
|
||||
// Obtener el cliente Memcached directamente
|
||||
@ -359,9 +434,11 @@ class CacheManagerService
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifica si un driver es soportado.
|
||||
* Verifica si un driver es soportado por el sistema.
|
||||
*
|
||||
* @param string $driver Nombre del driver a verificar
|
||||
* @return bool True si el driver es soportado, False en caso contrario
|
||||
*/
|
||||
private function isSupportedDriver(string $driver): bool
|
||||
{
|
||||
@ -369,7 +446,10 @@ class CacheManagerService
|
||||
}
|
||||
|
||||
/**
|
||||
* Convierte bytes en un formato legible.
|
||||
* Convierte bytes en un formato legible por humanos.
|
||||
*
|
||||
* @param int|float $bytes Cantidad de bytes a formatear
|
||||
* @return string Cantidad formateada con unidad (B, KB, MB, GB, TB)
|
||||
*/
|
||||
private function formatBytes($bytes)
|
||||
{
|
||||
@ -380,7 +460,12 @@ class CacheManagerService
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera una respuesta estandarizada.
|
||||
* Genera una respuesta estandarizada para las operaciones del servicio.
|
||||
*
|
||||
* @param string $status Estado de la operación ('success', 'warning', 'danger', 'info')
|
||||
* @param string $message Mensaje descriptivo de la operación
|
||||
* @param array $data Datos adicionales de la operación
|
||||
* @return array Respuesta estructurada con estado, mensaje y datos
|
||||
*/
|
||||
private function response(string $status, string $message, array $data = []): array
|
||||
{
|
||||
|
@ -9,28 +9,26 @@ use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
|
||||
/**
|
||||
* Servicio para gestionar la configuración global del sistema.
|
||||
*
|
||||
* Esta clase maneja las configuraciones globales del sistema, incluyendo servicios
|
||||
* externos (Facebook, Google), configuración de Vuexy y sistema de correo.
|
||||
* Implementa un sistema de caché para optimizar el rendimiento y proporciona
|
||||
* valores predeterminados cuando es necesario.
|
||||
*/
|
||||
class GlobalSettingsService
|
||||
{
|
||||
/**
|
||||
* Tiempo de vida del caché en minutos (30 días).
|
||||
*/
|
||||
/** @var int Tiempo de vida del caché en minutos (60 * 24 * 30 = 30 días) */
|
||||
private $cacheTTL = 60 * 24 * 30;
|
||||
|
||||
/**
|
||||
* Actualiza o crea una configuración.
|
||||
*/
|
||||
public function updateSetting(string $key, string $value): bool
|
||||
{
|
||||
$setting = Setting::updateOrCreate(
|
||||
['key' => $key],
|
||||
['value' => trim($value)]
|
||||
);
|
||||
|
||||
return $setting->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga y sobrescribe las configuraciones del sistema.
|
||||
* Carga la configuración del sistema desde la base de datos o caché.
|
||||
*
|
||||
* Gestiona la carga de configuraciones para servicios externos y Vuexy.
|
||||
* Si la base de datos no está inicializada, utiliza valores predeterminados.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function loadSystemConfig(): void
|
||||
{
|
||||
@ -41,7 +39,7 @@ class GlobalSettingsService
|
||||
} else {
|
||||
// Cargar configuración desde la caché o base de datos
|
||||
$config = Cache::remember('global_system_config', $this->cacheTTL, function () {
|
||||
$settings = Setting::global()
|
||||
$settings = Setting::withVirtualValue()
|
||||
->where('key', 'LIKE', 'config.%')
|
||||
->pluck('value', 'key')
|
||||
->toArray();
|
||||
@ -58,6 +56,7 @@ class GlobalSettingsService
|
||||
Config::set('services.facebook', $config['servicesFacebook']);
|
||||
Config::set('services.google', $config['servicesGoogle']);
|
||||
Config::set('vuexy', $config['vuexy']);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// Manejo silencioso de errores para evitar interrupciones
|
||||
Config::set('services.facebook', config('services.facebook', []));
|
||||
@ -67,7 +66,9 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Devuelve una configuración predeterminada si la base de datos no está inicializada.
|
||||
* Obtiene la configuración predeterminada del sistema.
|
||||
*
|
||||
* @return array Configuración predeterminada para servicios y Vuexy
|
||||
*/
|
||||
private function getDefaultSystemConfig(): array
|
||||
{
|
||||
@ -87,7 +88,11 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si un bloque de configuraciones está presente.
|
||||
* Verifica si existe configuración para un bloque específico.
|
||||
*
|
||||
* @param array $settings Array de configuraciones
|
||||
* @param string $blockPrefix Prefijo del bloque a verificar
|
||||
* @return bool True si existe configuración para el bloque
|
||||
*/
|
||||
protected function hasBlockConfig(array $settings, string $blockPrefix): bool
|
||||
{
|
||||
@ -95,13 +100,17 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye la configuración de un servicio (Facebook, Google, etc.).
|
||||
* Construye la configuración para un servicio específico.
|
||||
*
|
||||
* @param array $settings Array de configuraciones
|
||||
* @param string $blockPrefix Prefijo del bloque de configuración
|
||||
* @param string $defaultConfigKey Clave de configuración predeterminada
|
||||
* @return array Configuración del servicio
|
||||
*/
|
||||
protected function buildServiceConfig(array $settings, string $blockPrefix, string $defaultConfigKey): array
|
||||
{
|
||||
if (!$this->hasBlockConfig($settings, $blockPrefix)) {
|
||||
return [];
|
||||
return config($defaultConfigKey);
|
||||
return config($defaultConfigKey)?? [];
|
||||
}
|
||||
|
||||
return [
|
||||
@ -112,8 +121,14 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Construye la configuración personalizada de Vuexy.
|
||||
*/
|
||||
* Construye la configuración de Vuexy.
|
||||
*
|
||||
* Combina la configuración predeterminada con los valores almacenados
|
||||
* en la base de datos y normaliza los campos booleanos.
|
||||
*
|
||||
* @param array $settings Array de configuraciones
|
||||
* @return array Configuración de Vuexy normalizada
|
||||
*/
|
||||
protected function buildVuexyConfig(array $settings): array
|
||||
{
|
||||
// Configuración predeterminada del sistema
|
||||
@ -133,7 +148,10 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Normaliza los campos booleanos.
|
||||
* Normaliza los campos booleanos en la configuración.
|
||||
*
|
||||
* @param array $config Configuración a normalizar
|
||||
* @return array Configuración con campos booleanos normalizados
|
||||
*/
|
||||
protected function normalizeBooleanFields(array $config): array
|
||||
{
|
||||
@ -158,7 +176,9 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia el caché de la configuración del sistema.
|
||||
* Limpia la caché de configuración del sistema.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearSystemConfigCache(): void
|
||||
{
|
||||
@ -166,21 +186,29 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina las claves config.vuexy.* y limpia global_system_config
|
||||
* Limpia la configuración de Vuexy de la base de datos y caché.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearVuexyConfig(): void
|
||||
{
|
||||
Setting::where('key', 'LIKE', 'config.vuexy.%')->delete();
|
||||
|
||||
Cache::forget('global_system_config');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene y sobrescribe la configuración de correo electrónico.
|
||||
* Obtiene la configuración del sistema de correo.
|
||||
*
|
||||
* Recupera y estructura la configuración de correo incluyendo
|
||||
* configuración SMTP, direcciones de envío y respuesta.
|
||||
*
|
||||
* @return array Configuración completa del sistema de correo
|
||||
*/
|
||||
public function getMailSystemConfig(): array
|
||||
{
|
||||
return Cache::remember('mail_system_config', $this->cacheTTL, function () {
|
||||
$settings = Setting::global()
|
||||
$settings = Setting::withVirtualValue()
|
||||
->where('key', 'LIKE', 'mail.%')
|
||||
->pluck('value', 'key')
|
||||
->toArray();
|
||||
@ -215,7 +243,9 @@ class GlobalSettingsService
|
||||
}
|
||||
|
||||
/**
|
||||
* Limpia el caché de la configuración de correo electrónico.
|
||||
* Limpia la caché de configuración del sistema de correo.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function clearMailSystemConfigCache(): void
|
||||
{
|
||||
|
@ -10,12 +10,12 @@ class SessionManagerService
|
||||
{
|
||||
private string $driver;
|
||||
|
||||
public function __construct(string $driver = null)
|
||||
public function __construct(mixed $driver = null)
|
||||
{
|
||||
$this->driver = $driver ?? config('session.driver');
|
||||
}
|
||||
|
||||
public function getSessionStats(string $driver = null): array
|
||||
public function getSessionStats(mixed $driver = null): array
|
||||
{
|
||||
$driver = $driver ?? $this->driver;
|
||||
|
||||
@ -41,7 +41,7 @@ class SessionManagerService
|
||||
}
|
||||
}
|
||||
|
||||
public function clearSessions(string $driver = null): array
|
||||
public function clearSessions(mixed $driver = null): array
|
||||
{
|
||||
$driver = $driver ?? $this->driver;
|
||||
|
||||
|
190
Services/SettingsService.php
Normal file
190
Services/SettingsService.php
Normal file
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
class SettingsService
|
||||
{
|
||||
/**
|
||||
* Obtiene una configuración con opciones avanzadas de caché.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int|null $userId
|
||||
* @param string|null $category
|
||||
* @param bool $useCache
|
||||
* @param bool $storeInCache
|
||||
* @param int|null $cacheTtl
|
||||
* @return mixed|null
|
||||
*/
|
||||
public function get(
|
||||
string $key,
|
||||
?int $userId = null,
|
||||
?string $category = null,
|
||||
bool $useCache = false,
|
||||
bool $storeInCache = true,
|
||||
?int $cacheTtl = 120
|
||||
) {
|
||||
$cacheKey = $this->generateCacheKey($key, $userId, $category);
|
||||
|
||||
if ($useCache && Cache::has($cacheKey)) {
|
||||
return Cache::get($cacheKey);
|
||||
}
|
||||
|
||||
$value = $this->retrieveSetting($key, $userId, $category);
|
||||
|
||||
if ($storeInCache && $value !== null) {
|
||||
Cache::put($cacheKey, $value, now()->addMinutes($cacheTtl));
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda o actualiza una configuración con control de caché.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param int|null $userId
|
||||
* @param string|null $category
|
||||
* @param string|null $mimeType
|
||||
* @param string|null $fileName
|
||||
* @param bool $updateCache
|
||||
* @param int|null $cacheTtl
|
||||
* @return Setting|null
|
||||
*/
|
||||
public function set(
|
||||
string $key,
|
||||
mixed $value,
|
||||
?int $userId = null,
|
||||
?string $category = null,
|
||||
?string $mimeType = null,
|
||||
?string $fileName = null,
|
||||
bool $updateCache = false,
|
||||
?int $cacheTtl = 120
|
||||
): ?Setting {
|
||||
$data = [
|
||||
'user_id' => $userId,
|
||||
'category' => $category,
|
||||
'mime_type' => $mimeType,
|
||||
'file_name' => $fileName,
|
||||
// Inicializar todos los campos de valor como null
|
||||
'value_string' => null,
|
||||
'value_integer' => null,
|
||||
'value_boolean' => null,
|
||||
'value_float' => null,
|
||||
'value_text' => null,
|
||||
'value_binary' => null,
|
||||
];
|
||||
|
||||
// Detectar tipo de valor
|
||||
if (is_string($value)) {
|
||||
// Evaluamos la longitud de la cadena
|
||||
$threshold = 250;
|
||||
|
||||
if (strlen($value) > $threshold) {
|
||||
$data['value_text'] = $value;
|
||||
} else {
|
||||
$data['value_string'] = $value;
|
||||
}
|
||||
} elseif (is_int($value)) {
|
||||
$data['value_integer'] = $value;
|
||||
} elseif (is_bool($value)) {
|
||||
$data['value_boolean'] = $value;
|
||||
} elseif (is_float($value)) {
|
||||
$data['value_float'] = $value;
|
||||
} elseif (is_resource($value) || $value instanceof \SplFileInfo) {
|
||||
$data['value_binary'] = is_resource($value)
|
||||
? stream_get_contents($value)
|
||||
: file_get_contents($value->getRealPath());
|
||||
} elseif (is_array($value) || is_object($value)) {
|
||||
$data['value_text'] = json_encode($value);
|
||||
}
|
||||
|
||||
// Se registra usuario que realiza la acción
|
||||
if (Auth::check()) {
|
||||
$data['updated_by'] = Auth::id();
|
||||
}
|
||||
|
||||
$setting = Setting::updateOrCreate(
|
||||
['key' => $key, 'user_id' => $userId, 'category' => $category],
|
||||
$data
|
||||
);
|
||||
|
||||
if ($updateCache) {
|
||||
$cacheKey = $this->generateCacheKey($key, $userId, $category);
|
||||
Cache::put($cacheKey, $setting->value, now()->addMinutes($cacheTtl));
|
||||
}
|
||||
|
||||
return $setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina una configuración.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int|null $userId
|
||||
* @param string|null $category
|
||||
* @return Setting|null La configuración eliminada o null si no existía
|
||||
*/
|
||||
public function delete(string $key, ?int $userId = null, ?string $category = null): ?Setting
|
||||
{
|
||||
$setting = Setting::where('key', $key);
|
||||
|
||||
if ($userId !== null) {
|
||||
$setting->where('user_id', $userId);
|
||||
}
|
||||
|
||||
if ($category !== null) {
|
||||
$setting->where('category', $category);
|
||||
}
|
||||
|
||||
$setting = $setting->first();
|
||||
|
||||
if ($setting) {
|
||||
$cacheKey = $this->generateCacheKey($key, $userId, $category);
|
||||
Cache::forget($cacheKey);
|
||||
$setting->delete();
|
||||
}
|
||||
|
||||
return $setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recupera una configuración de la base de datos.
|
||||
*
|
||||
* @param string $key
|
||||
* @param integer|null $userId
|
||||
* @param string|null $category
|
||||
* @return void
|
||||
*/
|
||||
protected function retrieveSetting(string $key, ?int $userId, ?string $category)
|
||||
{
|
||||
$query = Setting::where('key', $key);
|
||||
|
||||
if ($userId !== null) {
|
||||
$query->where('user_id', $userId);
|
||||
}
|
||||
|
||||
if ($category !== null) {
|
||||
$query->where('category', $category);
|
||||
}
|
||||
|
||||
return $query->first()?->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera una clave de caché para una configuración.
|
||||
*
|
||||
* @param string $key
|
||||
* @param integer|null $userId
|
||||
* @param string|null $category
|
||||
* @return string
|
||||
*/
|
||||
protected function generateCacheKey(string $key, ?int $userId, ?string $category): string
|
||||
{
|
||||
return 'settings:' . md5($key . '|' . $userId . '|' . $category);
|
||||
}
|
||||
}
|
@ -2,10 +2,7 @@
|
||||
|
||||
namespace Koneko\VuexyAdmin\Services;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\Facades\{Auth,Cache,Route,Gate};
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
|
||||
class VuexyAdminService
|
||||
@ -26,12 +23,8 @@ class VuexyAdminService
|
||||
{
|
||||
$this->user = Auth::user();
|
||||
$this->vuexySearch = Auth::user() !== null;
|
||||
$this->orientation = config('vuexy.custom.myLayout');
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene el menú según el estado del usuario (autenticado o no).
|
||||
*/
|
||||
public function getMenu()
|
||||
{
|
||||
// Obtener el menú desde la caché
|
||||
@ -45,9 +38,6 @@ class VuexyAdminService
|
||||
return $this->markActive($menu, $currentRoute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Menú para usuarios no autenticados.dump
|
||||
*/
|
||||
private function getGuestMenu()
|
||||
{
|
||||
return Cache::remember('vuexy_menu_guest', now()->addDays(7), function () {
|
||||
@ -55,12 +45,9 @@ class VuexyAdminService
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Menú para usuarios autenticados.
|
||||
*/
|
||||
private function getUserMenu()
|
||||
{
|
||||
Cache::forget("vuexy_menu_user_{$this->user->id}"); // Borrar la caché anterior para actualizarla
|
||||
//Cache::forget("vuexy_menu_user_{$this->user->id}"); // Borrar la caché anterior para actualizarla
|
||||
|
||||
return Cache::remember("vuexy_menu_user_{$this->user->id}", now()->addHours(24), function () {
|
||||
return $this->getMenuArray();
|
||||
@ -89,9 +76,6 @@ class VuexyAdminService
|
||||
return $menu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalida el cache del menú de un usuario.
|
||||
*/
|
||||
public static function clearUserMenuCache()
|
||||
{
|
||||
$user = Auth::user();
|
||||
@ -100,9 +84,6 @@ class VuexyAdminService
|
||||
Cache::forget("vuexy_menu_user_{$user->id}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalida el cache del menú de invitados.
|
||||
*/
|
||||
public static function clearGuestMenuCache()
|
||||
{
|
||||
Cache::forget('vuexy_menu_guest');
|
||||
@ -224,7 +205,7 @@ class VuexyAdminService
|
||||
return $quickLinksData;
|
||||
}
|
||||
|
||||
private function collectQuickLinksFromMenu(array $menu, array &$quickLinks, string $parentTitle = null)
|
||||
private function collectQuickLinksFromMenu(array $menu, array &$quickLinks, mixed $parentTitle = null)
|
||||
{
|
||||
foreach ($menu as $title => $item) {
|
||||
// Verificar si el elemento está en la lista de quicklinksRouteNames
|
||||
@ -249,9 +230,6 @@ class VuexyAdminService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica si la ruta actual existe en la lista de enlaces.
|
||||
*/
|
||||
private function isCurrentPageInList(array $quickLinks, string $currentRoute): bool
|
||||
{
|
||||
foreach ($quickLinks['rows'] as $row) {
|
||||
@ -291,193 +269,8 @@ class VuexyAdminService
|
||||
<a class='nav-link btn btn-text-secondary btn-icon rounded-pill dropdown-toggle hide-arrow' href='javascript:void(0);' data-bs-toggle='dropdown' data-bs-auto-close='outside' aria-expanded='false'>
|
||||
<span class='position-relative'>
|
||||
<i class='ti ti-bell ti-md'></i>
|
||||
<span class='badge rounded-pill bg-danger badge-dot badge-notifications border'></span>
|
||||
</span>
|
||||
</a>
|
||||
<ul class='dropdown-menu dropdown-menu-end p-0'>
|
||||
<li class='dropdown-menu-header border-bottom'>
|
||||
<div class='dropdown-header d-flex align-items-center py-3'>
|
||||
<h6 class='mb-0 me-auto'>Notification</h6>
|
||||
<div class='d-flex align-items-center h6 mb-0'>
|
||||
<span class='badge bg-label-primary me-2'>8 New</span>
|
||||
<a href='javascript:void(0)' class='btn btn-text-secondary rounded-pill btn-icon dropdown-notifications-all' data-bs-toggle='tooltip' data-bs-placement='top' title='Mark all as read'><i class='ti ti-mail-opened text-heading'></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='dropdown-notifications-list scrollable-container'>
|
||||
<ul class='list-group list-group-flush'>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<img src='' . asset('assets/admin/img/avatars/1.png') . '' alt class='rounded-circle'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='small mb-1'>Congratulation Lettie 🎉</h6>
|
||||
<small class='mb-1 d-block text-body'>Won the monthly best seller gold badge</small>
|
||||
<small class='text-muted'>1h ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<span class='avatar-initial rounded-circle bg-label-danger'>CF</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>Charles Franklin</h6>
|
||||
<small class='mb-1 d-block text-body'>Accepted your connection</small>
|
||||
<small class='text-muted'>12hr ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item marked-as-read'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<img src='' . asset('assets/admin/img/avatars/2.png') . '' alt class='rounded-circle'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>New Message ✉️</h6>
|
||||
<small class='mb-1 d-block text-body'>You have new message from Natalie</small>
|
||||
<small class='text-muted'>1h ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<span class='avatar-initial rounded-circle bg-label-success'><i class='ti ti-shopping-cart'></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>Whoo! You have new order 🛒 </h6>
|
||||
<small class='mb-1 d-block text-body'>ACME Inc. made new order $1,154</small>
|
||||
<small class='text-muted'>1 day ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item marked-as-read'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<img src='' . asset('assets/admin/img/avatars/9.png') . '' alt class='rounded-circle'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>Application has been approved 🚀 </h6>
|
||||
<small class='mb-1 d-block text-body'>Your ABC project application has been approved.</small>
|
||||
<small class='text-muted'>2 days ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item marked-as-read'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<span class='avatar-initial rounded-circle bg-label-success'><i class='ti ti-chart-pie'></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>Monthly report is generated</h6>
|
||||
<small class='mb-1 d-block text-body'>July monthly financial report is generated </small>
|
||||
<small class='text-muted'>3 days ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item marked-as-read'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<img src='' . asset('assets/admin/img/avatars/5.png') . '' alt class='rounded-circle'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>Send connection request</h6>
|
||||
<small class='mb-1 d-block text-body'>Peter sent you connection request</small>
|
||||
<small class='text-muted'>4 days ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<img src='' . asset('assets/admin/img/avatars/6.png') . '' alt class='rounded-circle'>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>New message from Jane</h6>
|
||||
<small class='mb-1 d-block text-body'>Your have new message from Jane</small>
|
||||
<small class='text-muted'>5 days ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class='list-group-item list-group-item-action dropdown-notifications-item marked-as-read'>
|
||||
<div class='d-flex'>
|
||||
<div class='flex-shrink-0 me-3'>
|
||||
<div class='avatar'>
|
||||
<span class='avatar-initial rounded-circle bg-label-warning'><i class='ti ti-alert-triangle'></i></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='flex-grow-1'>
|
||||
<h6 class='mb-1 small'>CPU is running high</h6>
|
||||
<small class='mb-1 d-block text-body'>CPU Utilization Percent is currently at 88.63%,</small>
|
||||
<small class='text-muted'>5 days ago</small>
|
||||
</div>
|
||||
<div class='flex-shrink-0 dropdown-notifications-actions'>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-read'><span class='badge badge-dot'></span></a>
|
||||
<a href='javascript:void(0)' class='dropdown-notifications-archive'><span class='ti ti-x'></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class='border-top'>
|
||||
<div class='d-grid p-4'>
|
||||
<a class='btn btn-primary btn-sm d-flex' href='javascript:void(0);'>
|
||||
<small class='align-middle'>View all notifications</small>
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</li>";
|
||||
}
|
||||
|
||||
|
@ -14,9 +14,10 @@
|
||||
"owen-it/laravel-auditing": "^13.6",
|
||||
"spatie/laravel-permission": "^6.10"
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Koneko\\VuexyAdmin\\": ""
|
||||
"Koneko\\VuexyAdmin\\": "./"
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
@ -35,6 +36,5 @@
|
||||
"support": {
|
||||
"source": "https://github.com/koneko-mx/laravel-vuexy-admin",
|
||||
"issues": "https://github.com/koneko-mx/laravel-vuexy-admin/issues"
|
||||
},
|
||||
"prefer-stable": true
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +112,7 @@ return [
|
||||
],
|
||||
]
|
||||
],
|
||||
'Configuración de cuenta' => [
|
||||
'Cuenta de usuario' => [
|
||||
'route' => 'admin.core.user-profile.index',
|
||||
'icon' => 'menu-icon tf-icons ti ti-user-cog',
|
||||
],
|
||||
@ -377,14 +377,14 @@ return [
|
||||
'can' => 'admin.inventory.product-catalogs.view',
|
||||
],
|
||||
'Productos y servicios' => [
|
||||
'route' => 'admin.inventory.products.index',
|
||||
'route' => 'admin.products.products.index',
|
||||
'icon' => 'menu-icon tf-icons ti ti-packages',
|
||||
'can' => 'admin.inventory.products.view',
|
||||
'can' => 'admin.products.products.view',
|
||||
],
|
||||
'Agregar producto o servicio' => [
|
||||
'route' => 'admin.inventory.products.create',
|
||||
'route' => 'admin.products.products.create',
|
||||
'icon' => 'menu-icon tf-icons ti ti-package',
|
||||
'can' => 'admin.inventory.products.create',
|
||||
'can' => 'admin.products.products.create',
|
||||
],
|
||||
]
|
||||
],
|
||||
@ -616,16 +616,16 @@ return [
|
||||
'can' => 'admin.inventory.suppliers.view',
|
||||
],
|
||||
'Órdenes de Compra' => [
|
||||
'route' => 'admin.inventory.orders.index',
|
||||
'can' => 'admin.inventory.orders.view',
|
||||
'route' => 'admin.purchase-orders.orders.index',
|
||||
'can' => 'admin.purchase-orders.orders.view',
|
||||
],
|
||||
'Recepción de Productos' => [
|
||||
'route' => 'admin.inventory.reception.index',
|
||||
'can' => 'admin.inventory.reception.view',
|
||||
'route' => 'admin.purchase-orders.reception.index',
|
||||
'can' => 'admin.purchase-orders.reception.view',
|
||||
],
|
||||
'Gestión de Insumos' => [
|
||||
'route' => 'admin.inventory.materials.index',
|
||||
'can' => 'admin.inventory.materials.view',
|
||||
'route' => 'admin.purchase-orders.materials.index',
|
||||
'can' => 'admin.purchase-orders.materials.view',
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -654,20 +654,20 @@ return [
|
||||
'icon' => 'menu-icon tf-icons ti ti-truck',
|
||||
'submenu' => [
|
||||
'Órdenes de Envío' => [
|
||||
'route' => 'admin.inventory.shipping-orders.index',
|
||||
'can' => 'admin.inventory.shipping-orders.view',
|
||||
'route' => 'admin.shipping.orders.index',
|
||||
'can' => 'admin.shipping.orders.view',
|
||||
],
|
||||
'Seguimiento de Envíos' => [
|
||||
'route' => 'admin.inventory.shipping-tracking.index',
|
||||
'can' => 'admin.inventory.shipping-tracking.view',
|
||||
'route' => 'admin.shipping.tracking.index',
|
||||
'can' => 'admin.shipping.tracking.view',
|
||||
],
|
||||
'Transportistas' => [
|
||||
'route' => 'admin.inventory.shipping-carriers.index',
|
||||
'can' => 'admin.inventory.shipping-carriers.view',
|
||||
'route' => 'admin.shipping.carriers.index',
|
||||
'can' => 'admin.shipping.carriers.view',
|
||||
],
|
||||
'Tarifas y Métodos de Envío' => [
|
||||
'route' => 'admin.inventory.shipping-rates.index',
|
||||
'can' => 'admin.inventory.shipping-rates.view',
|
||||
'route' => 'admin.shipping.rates.index',
|
||||
'can' => 'admin.shipping.rates.view',
|
||||
],
|
||||
],
|
||||
],
|
||||
@ -679,16 +679,16 @@ return [
|
||||
'can' => 'admin.inventory.asset.view',
|
||||
],
|
||||
'Mantenimiento Preventivo' => [
|
||||
'route' => 'admin.inventory.asset-maintenance.index',
|
||||
'can' => 'admin.inventory.asset-maintenance.view',
|
||||
'route' => 'admin.assets.maintenance.index',
|
||||
'can' => 'admin.assets.maintenance.view',
|
||||
],
|
||||
'Control de Vida Útil' => [
|
||||
'route' => 'admin.inventory.asset-lifecycle.index',
|
||||
'can' => 'admin.inventory.asset-lifecycle.view',
|
||||
'route' => 'admin.assets.lifecycle.index',
|
||||
'can' => 'admin.assets.lifecycle.view',
|
||||
],
|
||||
'Asignación de Activos' => [
|
||||
'route' => 'admin.inventory.asset-assignments.index',
|
||||
'can' => 'admin.inventory.asset-assignments.view',
|
||||
'route' => 'admin.assets.assignments.index',
|
||||
'can' => 'admin.assets.assignments.view',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
@ -59,8 +59,8 @@
|
||||
"admin.attendance.absences.view",
|
||||
"admin.inventory.product-categories.view",
|
||||
"admin.inventory.product-catalogs.view",
|
||||
"admin.inventory.products.view",
|
||||
"admin.inventory.products.create",
|
||||
"admin.products.products.view",
|
||||
"admin.products.products.create",
|
||||
"admin.sales.dashboard.allow",
|
||||
"admin.contacts.customers.view",
|
||||
"admin.sales.sales.view",
|
||||
@ -99,21 +99,21 @@
|
||||
"admin.billing.nomina.view",
|
||||
"admin.billing.verify-cfdi.allow",
|
||||
"admin.contacts.suppliers.view",
|
||||
"admin.inventory.orders.view",
|
||||
"admin.inventory.reception.view",
|
||||
"admin.inventory.materials.view",
|
||||
"admin.purchase-orders.orders.view",
|
||||
"admin.purchase-orders.reception.view",
|
||||
"admin.purchase-orders.materials.view",
|
||||
"admin.inventory.warehouse.view",
|
||||
"admin.inventory.stock.view",
|
||||
"admin.inventory.movements.view",
|
||||
"admin.inventory.transfers.view",
|
||||
"admin.inventory.shipping-orders.view",
|
||||
"admin.inventory.shipping-tracking.view",
|
||||
"admin.inventory.shipping-carriers.view",
|
||||
"admin.inventory.shipping-rates.view",
|
||||
"admin.inventory.assets.view",
|
||||
"admin.inventory.asset-maintenance.view",
|
||||
"admin.inventory.asset-lifecycle.view",
|
||||
"admin.inventory.asset-assignments.view",
|
||||
"admin.shipping.orders.view",
|
||||
"admin.shipping.tracking.view",
|
||||
"admin.shipping.carriers.view",
|
||||
"admin.shipping.rates.view",
|
||||
"admin.assets.assets.view",
|
||||
"admin.assets.maintenance.view",
|
||||
"admin.assets.lifecycle.view",
|
||||
"admin.assets.assignments.view",
|
||||
"admin.projects.dashboard.view",
|
||||
"admin.projects.view",
|
||||
"admin.projects.create",
|
||||
@ -167,21 +167,21 @@
|
||||
"admin.rrhh.organization.view",
|
||||
"admin.inventory.product-categories.view",
|
||||
"admin.inventory.product-catalogs.view",
|
||||
"admin.inventory.products.view",
|
||||
"admin.inventory.products.create",
|
||||
"admin.products.products.view",
|
||||
"admin.products.products.create",
|
||||
"admin.contacts.suppliers.view",
|
||||
"admin.contacts.suppliers.create",
|
||||
"admin.inventory.warehouse.view",
|
||||
"admin.inventory.orders.view",
|
||||
"admin.inventory.reception.view",
|
||||
"admin.inventory.materials.view",
|
||||
"admin.purchase-orders.orders.view",
|
||||
"admin.purchase-orders.reception.view",
|
||||
"admin.purchase-orders.materials.view",
|
||||
"admin.inventory.stock.view",
|
||||
"admin.inventory.movements.view",
|
||||
"admin.inventory.transfers.view",
|
||||
"admin.inventory.assets.view",
|
||||
"admin.inventory.asset-maintenance.view",
|
||||
"admin.inventory.asset-lifecycle.view",
|
||||
"admin.inventory.asset-assignments.view"
|
||||
"admin.assets.assets.view",
|
||||
"admin.assets.maintenance.view",
|
||||
"admin.assets.lifecycle.view",
|
||||
"admin.assets.assignments.view"
|
||||
]
|
||||
},
|
||||
"Administrador Web" : {
|
||||
@ -197,8 +197,8 @@
|
||||
"permissions" : [
|
||||
"admin.inventory.product-categories.view",
|
||||
"admin.inventory.product-catalogs.view",
|
||||
"admin.inventory.products.view",
|
||||
"admin.inventory.products.create",
|
||||
"admin.products.products.view",
|
||||
"admin.products.products.create",
|
||||
"admin.inventory.warehouse.view",
|
||||
"admin.inventory.stock.view",
|
||||
"admin.inventory.movements.view",
|
||||
@ -293,7 +293,7 @@
|
||||
"admin.attendance.absences.view",
|
||||
"admin.inventory.product-categories.view",
|
||||
"admin.inventory.product-catalogs.view",
|
||||
"admin.inventory.products.view",
|
||||
"admin.products.products.view",
|
||||
"admin.contacts.customers.view",
|
||||
"admin.sales.sales.view",
|
||||
"admin.sales.quotes.view",
|
||||
@ -322,21 +322,21 @@
|
||||
"admin.billing.pagos.view",
|
||||
"admin.billing.nomina.view",
|
||||
"admin.contacts.suppliers.view",
|
||||
"admin.inventory.orders.view",
|
||||
"admin.inventory.reception.view",
|
||||
"admin.inventory.materials.view",
|
||||
"admin.purchase-orders.orders.view",
|
||||
"admin.purchase-orders.reception.view",
|
||||
"admin.purchase-orders.materials.view",
|
||||
"admin.inventory.warehouse.view",
|
||||
"admin.inventory.stock.view",
|
||||
"admin.inventory.movements.view",
|
||||
"admin.inventory.transfers.view",
|
||||
"admin.inventory.shipping-orders.view",
|
||||
"admin.inventory.shipping-tracking.view",
|
||||
"admin.inventory.shipping-carriers.view",
|
||||
"admin.inventory.shipping-rates.view",
|
||||
"admin.inventory.assets.view",
|
||||
"admin.inventory.asset-maintenance.view",
|
||||
"admin.inventory.asset-lifecycle.view",
|
||||
"admin.inventory.asset-assignments.view",
|
||||
"admin.shipping.orders.view",
|
||||
"admin.shipping.tracking.view",
|
||||
"admin.shipping.carriers.view",
|
||||
"admin.shipping.rates.view",
|
||||
"admin.assets.assets.view",
|
||||
"admin.assets.maintenance.view",
|
||||
"admin.assets.lifecycle.view",
|
||||
"admin.assets.assignments.view",
|
||||
"admin.projects.dashboard.view",
|
||||
"admin.projects.view",
|
||||
"admin.projects.tasks.view",
|
||||
@ -421,8 +421,8 @@
|
||||
"admin.attendance.absences.view",
|
||||
"admin.inventory.product-categories.view",
|
||||
"admin.inventory.product-catalogs.view",
|
||||
"admin.inventory.products.view",
|
||||
"admin.inventory.products.create",
|
||||
"admin.products.products.view",
|
||||
"admin.products.products.create",
|
||||
"admin.sales.dashboard.allow",
|
||||
"admin.contacts.customers.view",
|
||||
"admin.contacts.customers.create",
|
||||
@ -463,21 +463,21 @@
|
||||
"admin.billing.verify-cfdi.allow",
|
||||
"admin.contacts.suppliers.view",
|
||||
"admin.contacts.suppliers.create",
|
||||
"admin.inventory.orders.view",
|
||||
"admin.inventory.reception.view",
|
||||
"admin.inventory.materials.view",
|
||||
"admin.purchase-orders.orders.view",
|
||||
"admin.purchase-orders.reception.view",
|
||||
"admin.purchase-orders.materials.view",
|
||||
"admin.inventory.warehouse.view",
|
||||
"admin.inventory.stock.view",
|
||||
"admin.inventory.movements.view",
|
||||
"admin.inventory.transfers.view",
|
||||
"admin.inventory.shipping-orders.view",
|
||||
"admin.inventory.shipping-tracking.view",
|
||||
"admin.inventory.shipping-carriers.view",
|
||||
"admin.inventory.shipping-rates.view",
|
||||
"admin.inventory.assets.view",
|
||||
"admin.inventory.asset-maintenance.view",
|
||||
"admin.inventory.asset-lifecycle.view",
|
||||
"admin.inventory.asset-assignments.view",
|
||||
"admin.shipping.orders.view",
|
||||
"admin.shipping.tracking.view",
|
||||
"admin.shipping.carriers.view",
|
||||
"admin.shipping.rates.view",
|
||||
"admin.assets.assets.view",
|
||||
"admin.assets.maintenance.view",
|
||||
"admin.assets.lifecycle.view",
|
||||
"admin.assets.assignments.view",
|
||||
"admin.projects.dashboard.view",
|
||||
"admin.projects.view",
|
||||
"admin.projects.create",
|
||||
|
@ -3,6 +3,7 @@
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
@ -12,18 +13,50 @@ return new class extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('settings', function (Blueprint $table) {
|
||||
$table->mediumIncrements('id');
|
||||
$table->smallIncrements('id');
|
||||
|
||||
// Clave del setting
|
||||
$table->string('key')->index();
|
||||
$table->text('value');
|
||||
|
||||
// Categoría (opcional pero recomendable)
|
||||
$table->string('category')->nullable()->index();
|
||||
|
||||
// Usuario (null para globales)
|
||||
$table->unsignedMediumInteger('user_id')->nullable()->index();
|
||||
|
||||
// Unique constraints
|
||||
$table->unique(['user_id', 'key']);
|
||||
// Valores segmentados por tipo para mejor rendimiento
|
||||
$table->string('value_string')->nullable();
|
||||
$table->integer('value_integer')->nullable();
|
||||
$table->boolean('value_boolean')->nullable();
|
||||
$table->float('value_float', 16, 8)->nullable();
|
||||
$table->text('value_text')->nullable();
|
||||
$table->binary('value_binary')->nullable();
|
||||
$table->string('mime_type', 50)->nullable();
|
||||
$table->string('file_name')->nullable();
|
||||
|
||||
// Auditoría
|
||||
$table->timestamps();
|
||||
$table->unsignedMediumInteger('updated_by')->nullable();
|
||||
|
||||
// Unique constraint para evitar duplicados
|
||||
$table->unique(['key', 'user_id', 'category']);
|
||||
|
||||
// Relaciones
|
||||
$table->foreign('user_id')->references('id')->on('users');
|
||||
$table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete();
|
||||
});
|
||||
|
||||
// Agregar columna virtual unificada
|
||||
DB::statement("ALTER TABLE settings ADD COLUMN value VARCHAR(255) GENERATED ALWAYS AS (
|
||||
CASE
|
||||
WHEN value_string IS NOT NULL THEN value_string
|
||||
WHEN value_integer IS NOT NULL THEN CAST(value_integer AS CHAR)
|
||||
WHEN value_boolean IS NOT NULL THEN IF(value_boolean, 'true', 'false')
|
||||
WHEN value_float IS NOT NULL THEN CAST(value_float AS CHAR)
|
||||
WHEN value_text IS NOT NULL THEN LEFT(value_text, 255)
|
||||
WHEN value_binary IS NOT NULL THEN '[binary_data]'
|
||||
ELSE NULL
|
||||
END
|
||||
) VIRTUAL");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('media_items', function (Blueprint $table) {
|
||||
$table->mediumIncrements('id');
|
||||
|
||||
// Relación polimórfica
|
||||
$table->unsignedMediumInteger('mediaable_id');
|
||||
$table->string('mediaable_type');
|
||||
|
||||
$table->unsignedTinyInteger('type')->index(); // Tipo de medio: 'image', 'video', 'file', 'youtube'
|
||||
$table->unsignedTinyInteger('sub_type')->index(); // Subtipo de medio: 'thumbnail', 'main', 'additional'
|
||||
|
||||
$table->string('url', 255)->nullable(); // URL del medio
|
||||
$table->string('path')->nullable(); // Ruta del archivo si está almacenado localmente
|
||||
|
||||
$table->string('title')->nullable()->index(); // Título del medio
|
||||
$table->mediumText('description')->nullable(); // Descripción del medio
|
||||
$table->unsignedTinyInteger('order')->nullable(); // Orden de presentación
|
||||
|
||||
// Authoría
|
||||
$table->timestamps();
|
||||
|
||||
// Índices
|
||||
$table->index(['mediaable_type', 'mediaable_id']);
|
||||
$table->index(['mediaable_type', 'mediaable_id', 'type']);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('images');
|
||||
}
|
||||
};
|
@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Koneko\VuexyAdmin\Models\Setting;
|
||||
|
||||
class SettingSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$settings_array = [
|
||||
//
|
||||
];
|
||||
|
||||
foreach ($settings_array as $key => $value) {
|
||||
Setting::create([
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
]);
|
||||
};
|
||||
}
|
||||
}
|
@ -106,7 +106,8 @@ class BootstrapTableManager {
|
||||
* Carga los formatters dinámicamente
|
||||
*/
|
||||
async loadFormatters() {
|
||||
const formattersModules = import.meta.glob('../../../../../**/resources/assets/js/bootstrap-table/*Formatters.js');
|
||||
//const formattersModules = import.meta.glob('../../../../../**/resources/assets/js/bootstrap-table/*Formatters.js');
|
||||
const formattersModules = import.meta.glob('/vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/*Formatters.js');
|
||||
|
||||
const formatterPromises = Object.entries(formattersModules).map(async ([path, importer]) => {
|
||||
const module = await importer();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { booleanStatusCatalog, statusIntBadgeBgCatalogCss, statusIntBadgeBgCatalog } from './globalConfig';
|
||||
import {routes} from '../../../../../laravel-vuexy-admin/resources/assets/js/bootstrap-table/globalConfig.js';
|
||||
import {routes} from '@vuexy-admin/bootstrap-table/globalConfig.js';
|
||||
|
||||
export const userActionFormatter = (value, row, index) => {
|
||||
if (!row.id) return '';
|
||||
|
@ -10,7 +10,7 @@
|
||||
// JS global variables
|
||||
window.config = {
|
||||
colors: {
|
||||
primary: '#7367f0',
|
||||
primary: '#155dfc',
|
||||
secondary: '#808390',
|
||||
success: '#28c76f',
|
||||
info: '#00bad1',
|
||||
@ -27,7 +27,7 @@ window.config = {
|
||||
borderColor: '#e6e6e8'
|
||||
},
|
||||
colors_label: {
|
||||
primary: '#7367f029',
|
||||
primary: '#155dfc29',
|
||||
secondary: '#a8aaae29',
|
||||
success: '#28c76f29',
|
||||
info: '#00cfe829',
|
||||
|
79
resources/assets/js/forms/disabledForm.js
Normal file
79
resources/assets/js/forms/disabledForm.js
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Previene interacción con el elemento.
|
||||
* @param {Event} event - El evento de clic.
|
||||
*/
|
||||
const preventInteraction = (event) => event.preventDefault();
|
||||
|
||||
/**
|
||||
* Habilita o deshabilita un select con Select2.
|
||||
* @param {HTMLElement} selectElement - El select afectado.
|
||||
* @param {boolean} disabled - Si debe ser deshabilitado.
|
||||
*/
|
||||
const toggleSelect2Disabled = (selectElement, disabled) => {
|
||||
selectElement.disabled = disabled;
|
||||
selectElement.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Aplica modo solo lectura a un select estándar o Select2.
|
||||
* @param {HTMLElement} select - El select a modificar.
|
||||
* @param {boolean} readonly - Si debe estar en modo solo lectura.
|
||||
*/
|
||||
const setSelectReadonly = (select, readonly) => {
|
||||
select.setAttribute('readonly-mode', readonly);
|
||||
select.style.pointerEvents = readonly ? 'none' : '';
|
||||
select.tabIndex = readonly ? -1 : '';
|
||||
|
||||
if (select.classList.contains('select2-hidden-accessible')) {
|
||||
toggleSelect2Disabled(select, readonly);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aplica modo solo lectura a un checkbox o radio.
|
||||
* @param {HTMLElement} checkbox - El input a modificar.
|
||||
* @param {boolean} readonly - Si debe ser solo lectura.
|
||||
*/
|
||||
const setCheckboxReadonly = (checkbox, readonly) => {
|
||||
checkbox.setAttribute('readonly-mode', readonly);
|
||||
checkbox.style.pointerEvents = readonly ? 'none' : '';
|
||||
checkbox[readonly ? 'addEventListener' : 'removeEventListener']('click', preventInteraction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Deshabilita o pone en modo de solo lectura los campos del formulario.
|
||||
* @param {string} formSelector - Selector del formulario a deshabilitar.
|
||||
*/
|
||||
export default function disableForm(formSelector) {
|
||||
const form = document.querySelector(formSelector);
|
||||
if (!form) {
|
||||
console.warn(`Formulario no encontrado con el selector: ${formSelector}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const inputs = form.querySelectorAll('input, textarea, select');
|
||||
|
||||
inputs.forEach((el) => {
|
||||
if (!el.classList.contains('data-always-enabled')) {
|
||||
switch (el.tagName) {
|
||||
case 'SELECT':
|
||||
if (el.classList.contains('select2')) {
|
||||
toggleSelect2Disabled(el, true);
|
||||
} else {
|
||||
setSelectReadonly(el, true);
|
||||
}
|
||||
break;
|
||||
case 'INPUT':
|
||||
if (['checkbox', 'radio'].includes(el.type)) {
|
||||
setCheckboxReadonly(el, true);
|
||||
} else {
|
||||
el.readOnly = true;
|
||||
}
|
||||
break;
|
||||
case 'TEXTAREA':
|
||||
el.readOnly = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
@ -155,7 +155,19 @@ export default class FormCustomListener {
|
||||
this.setButtonLoadingState(saveButton, true); // Poner en estado de carga al botón anfitrión
|
||||
|
||||
// Enviar la solicitud de Livewire correspondiente al enviar el formulario
|
||||
Livewire.dispatch(this.config.dispatchOnSubmit);
|
||||
const componentEl = form.closest('[wire\\:id]');
|
||||
const componentId = componentEl?.getAttribute('wire:id');
|
||||
|
||||
if (componentId) {
|
||||
const component = Livewire.find(componentId);
|
||||
if (component) {
|
||||
component.call(this.config.dispatchOnSubmit);
|
||||
} else {
|
||||
console.warn('No se encontró el componente Livewire.');
|
||||
}
|
||||
} else {
|
||||
console.warn('No se pudo encontrar wire:id para ejecutar el método Livewire.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -188,7 +200,10 @@ export default class FormCustomListener {
|
||||
*/
|
||||
toggleButtonsState(buttons, isEnabled) {
|
||||
buttons.forEach(button => {
|
||||
if (button) button.disabled = !isEnabled;
|
||||
if (button){
|
||||
button.disabled = !isEnabled;
|
||||
button.classList.toggle('disabled', !isEnabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -217,24 +232,57 @@ export default class FormCustomListener {
|
||||
*/
|
||||
initializeValidation(form) {
|
||||
if (this.config.validationConfig) {
|
||||
this.formValidationInstance = FormValidation.formValidation(form, this.config.validationConfig).on(
|
||||
'core.form.valid',
|
||||
() => this.handleFormValid(form)
|
||||
);
|
||||
this.formValidationInstance = FormValidation.formValidation(
|
||||
form,
|
||||
this.config.validationConfig
|
||||
).on('core.form.valid', () => this.handleFormValid(form));
|
||||
|
||||
// Aplicamos el fix después de un pequeño delay
|
||||
setTimeout(() => {
|
||||
this.fixValidationMessagePosition(form);
|
||||
}, 100); // Lo suficiente para esperar a que FV inserte los mensajes
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mueve los mensajes de error fuera del input-group para evitar romper el diseño
|
||||
*/
|
||||
fixValidationMessagePosition(form) {
|
||||
const groups = form.querySelectorAll('.input-group.has-validation');
|
||||
|
||||
groups.forEach(group => {
|
||||
const errorContainer = group.querySelector('.fv-plugins-message-container');
|
||||
|
||||
if (errorContainer) {
|
||||
// Evita duplicados
|
||||
if (errorContainer.classList.contains('moved')) return;
|
||||
|
||||
// Crear un contenedor si no existe
|
||||
let target = group.parentElement.querySelector('.fv-message');
|
||||
if (!target) {
|
||||
target = document.createElement('div');
|
||||
target.className = 'fv-message invalid-feedback';
|
||||
group.parentElement.appendChild(target);
|
||||
}
|
||||
|
||||
target.appendChild(errorContainer);
|
||||
errorContainer.classList.add('moved'); // Marcar como ya movido
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reloadValidation() {
|
||||
const form = document.querySelector(this.config.formSelector);
|
||||
|
||||
// Verificar si el formulario existe y si la validación está inicializada
|
||||
if (form && this.formValidationInstance) {
|
||||
try {
|
||||
// En lugar de destruir la validación, simplemente reiniciamos la validación.
|
||||
this.formValidationInstance.resetForm(); // Resetear el formulario, limpiando los errores
|
||||
setTimeout(() => {
|
||||
this.formValidationInstance.resetForm(); // Limpiar errores
|
||||
this.initializeValidation(form); // Reinicializar
|
||||
|
||||
// Reinicializar la validación con la configuración actual
|
||||
this.initializeValidation(form);
|
||||
// 🔁 Reconectar eventos de inputs y botones
|
||||
this.initFormEvents(form);
|
||||
}, 1);
|
||||
} catch (error) {
|
||||
console.error('Error al reiniciar la validación:', error);
|
||||
}
|
||||
@ -242,4 +290,5 @@ export default class FormCustomListener {
|
||||
console.warn('Formulario no encontrado o instancia de validación no disponible.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
10
resources/assets/js/forms/slugify.js
Normal file
10
resources/assets/js/forms/slugify.js
Normal file
@ -0,0 +1,10 @@
|
||||
window.createSlug = function(string) {
|
||||
return string
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[^a-z0-9\s-]/g, '')
|
||||
.trim()
|
||||
.replace(/\s+/g, '-')
|
||||
.replace(/-+/g, '-');
|
||||
};
|
27
resources/assets/js/livewire/registerLivewireHookOnce.js
Normal file
27
resources/assets/js/livewire/registerLivewireHookOnce.js
Normal file
@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Registra un hook Livewire solo una vez por componente.
|
||||
* @param {string} hookName - Nombre del hook Livewire (por ejemplo, "morphed").
|
||||
* @param {string} componentName - Nombre exacto del componente Livewire.
|
||||
* @param {function} callback - Función que se ejecutará una vez por hook+componente.
|
||||
*/
|
||||
export default function registerLivewireHookOnce(hookName, componentName, callback) {
|
||||
if (!hookName || !componentName || typeof callback !== 'function') {
|
||||
console.warn('[registerLivewireHookOnce] Parámetros inválidos.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clave única para este hook+componente
|
||||
const safeName = componentName.replace(/[^a-zA-Z0-9]/g, '_');
|
||||
const key = `__hook_${hookName}_${safeName}`;
|
||||
|
||||
if (!window[key]) {
|
||||
window[key] = true;
|
||||
|
||||
Livewire.hook(hookName, ({ component }) => {
|
||||
if (component.name === componentName) {
|
||||
// console.info(`[Livewire Hook Triggered] ${hookName} for ${component.name}`);
|
||||
callback(component);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -1,133 +0,0 @@
|
||||
import './../../vendor/libs/leaflet/leaflet'
|
||||
|
||||
export const LeafletMapHelper = (() => {
|
||||
let mapInstance, markerInstance;
|
||||
|
||||
const DEFAULT_COORDS = [19.4326, -99.1332]; // Coordenadas de CDMX por defecto
|
||||
|
||||
// Valida coordenadas
|
||||
const isValidCoordinate = (lat, lng) => {
|
||||
return lat && !isNaN(lat) && lat >= -90 && lat <= 90 && lat !== 0 &&
|
||||
lng && !isNaN(lng) && lng >= -180 && lng <= 180 && lng !== 0;
|
||||
};
|
||||
|
||||
// Crea opciones del mapa según el modo
|
||||
const getMapOptions = (mode) => ({
|
||||
scrollWheelZoom: mode !== 'delete',
|
||||
dragging: mode !== 'delete',
|
||||
doubleClickZoom: mode !== 'delete',
|
||||
boxZoom: mode !== 'delete',
|
||||
keyboard: mode !== 'delete',
|
||||
zoomControl: mode !== 'delete',
|
||||
touchZoom: mode !== 'delete'
|
||||
});
|
||||
|
||||
// Destruir el mapa existente
|
||||
const destroyMap = () => {
|
||||
if (mapInstance) {
|
||||
mapInstance.off();
|
||||
mapInstance.remove();
|
||||
mapInstance = null;
|
||||
}
|
||||
removeMarker();
|
||||
};
|
||||
|
||||
// Crear marcador en el mapa
|
||||
const createMarker = (lat, lng, draggable = false, onDragEnd) => {
|
||||
if (isValidCoordinate(lat, lng)) {
|
||||
markerInstance = L.marker([lat, lng], { draggable }).addTo(mapInstance)
|
||||
.bindPopup('<b>Ubicación seleccionada</b>').openPopup();
|
||||
|
||||
if (draggable && onDragEnd) {
|
||||
markerInstance.on('dragend', (e) => {
|
||||
const { lat, lng } = e.target.getLatLng();
|
||||
onDragEnd(lat, lng);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Eliminar marcador
|
||||
const removeMarker = () => {
|
||||
if (markerInstance) {
|
||||
markerInstance.remove();
|
||||
markerInstance = null;
|
||||
}
|
||||
};
|
||||
|
||||
// Actualizar coordenadas en formulario
|
||||
const updateCoordinates = (lat, lng, latSelector, lngSelector, livewireInstance) => {
|
||||
const latInput = document.querySelector(latSelector);
|
||||
const lngInput = document.querySelector(lngSelector);
|
||||
|
||||
if (!latInput || !lngInput) {
|
||||
console.warn(`⚠️ No se encontró el elemento del DOM para latitud (${latSelector}) o longitud (${lngSelector})`);
|
||||
return;
|
||||
}
|
||||
|
||||
latInput.value = lat ? lat.toFixed(6) : '';
|
||||
lngInput.value = lng ? lng.toFixed(6) : '';
|
||||
|
||||
if (livewireInstance) {
|
||||
livewireInstance.lat = lat ? lat.toFixed(6) : null;
|
||||
livewireInstance.lng = lng ? lng.toFixed(6) : null;
|
||||
}
|
||||
};
|
||||
|
||||
// Inicializar el mapa
|
||||
const initializeMap = (locationInputs, mode = 'create', livewireInstance = null) => {
|
||||
const mapElement = document.getElementById(locationInputs.mapId);
|
||||
|
||||
if (!mapElement) {
|
||||
console.error(`❌ No se encontró el contenedor del mapa con ID: ${locationInputs.mapId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
let latElement = document.querySelector(locationInputs.lat);
|
||||
let lngElement = document.querySelector(locationInputs.lng);
|
||||
|
||||
if (!latElement || !lngElement) {
|
||||
console.error(`❌ No se encontraron los campos de latitud (${locationInputs.lat}) o longitud (${locationInputs.lng})`);
|
||||
return;
|
||||
}
|
||||
|
||||
let lat = parseFloat(latElement.value);
|
||||
let lng = parseFloat(lngElement.value);
|
||||
|
||||
const mapCoords = isValidCoordinate(lat, lng) ? [lat, lng] : DEFAULT_COORDS;
|
||||
const zoomLevel = isValidCoordinate(lat, lng) ? 16 : 13;
|
||||
|
||||
if (mapInstance) destroyMap();
|
||||
|
||||
mapInstance = L.map(locationInputs.mapId, getMapOptions(mode)).setView(mapCoords, zoomLevel);
|
||||
L.tileLayer('https://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(mapInstance);
|
||||
|
||||
if (mode !== 'create') createMarker(lat, lng, mode === 'edit', (lat, lng) => updateCoordinates(lat, lng, locationInputs.lat, locationInputs.lng, livewireInstance));
|
||||
|
||||
if (mode !== 'delete') {
|
||||
mapInstance.on('click', (e) => {
|
||||
const { lat, lng } = e.latlng;
|
||||
removeMarker();
|
||||
createMarker(lat, lng, true, (lat, lng) => updateCoordinates(lat, lng, locationInputs.lat, locationInputs.lng, livewireInstance));
|
||||
updateCoordinates(lat, lng, locationInputs.lat, locationInputs.lng, livewireInstance);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
const btnClearElement = document.querySelector(locationInputs.btnClear);
|
||||
|
||||
if(!btnClearElement){
|
||||
console.error(`❌ No se encontró el botón de limpiar con ID: ${locationInputs.btnClear}`);return;
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
||||
return {
|
||||
initializeMap,
|
||||
clearCoordinates: () => {
|
||||
removeMarker();
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
window.LeafletMapHelper = LeafletMapHelper;
|
@ -1,12 +0,0 @@
|
||||
export class LocationIQSearchHelper {
|
||||
constructor(apiKey) {
|
||||
this.apiKey = apiKey;
|
||||
this.baseUrl = 'https://us1.locationiq.com/v1/search.php';
|
||||
}
|
||||
|
||||
async searchAddress(query) {
|
||||
const response = await fetch(`${this.baseUrl}?key=${this.apiKey}&q=${query}&format=json`);
|
||||
if (!response.ok) throw new Error('Error al buscar la dirección');
|
||||
return await response.json();
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ $grays: (
|
||||
// scss-docs-start color-variables
|
||||
$blue: #007bff !default;
|
||||
$indigo: #6610f2 !default;
|
||||
$purple: #7367f0 !default;
|
||||
$purple: #64748b !default;
|
||||
$pink: #e83e8c !default;
|
||||
$red: #ff4c51 !default;
|
||||
$orange: #fd7e14 !default;
|
||||
|
@ -35,7 +35,7 @@ $grays: (
|
||||
// scss-docs-start color-variables
|
||||
$blue: #007bff !default;
|
||||
$indigo: #6610f2 !default;
|
||||
$purple: #7367f0 !default;
|
||||
$purple: #64748b !default;
|
||||
$pink: #e83e8c !default;
|
||||
$red: #ff4c51 !default;
|
||||
$orange: #fd7e14 !default;
|
||||
|
2
resources/assets/vendor/scss/core-dark.scss
vendored
2
resources/assets/vendor/scss/core-dark.scss
vendored
@ -2,3 +2,5 @@
|
||||
@import 'bootstrap-extended-dark';
|
||||
@import 'components-dark';
|
||||
@import 'colors-dark';
|
||||
|
||||
$primary-color: #4a8c08;
|
||||
|
2
resources/assets/vendor/scss/core.scss
vendored
2
resources/assets/vendor/scss/core.scss
vendored
@ -2,3 +2,5 @@
|
||||
@import 'bootstrap-extended';
|
||||
@import 'components';
|
||||
@import 'colors';
|
||||
|
||||
$primary-color: #4a8c08;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
|
||||
body {
|
||||
background: $card-bg;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
$body-bg: #f8f7fa;
|
||||
|
||||
body {
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
|
||||
body {
|
||||
background: $body-bg;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
$body-bg: #f8f7fa;
|
||||
|
||||
body {
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
|
||||
body {
|
||||
background: $body-bg;
|
||||
|
@ -4,7 +4,7 @@
|
||||
@import './_theme/pages';
|
||||
@import './_theme/_theme';
|
||||
|
||||
$primary-color: #7367f0;
|
||||
$primary-color: #155dfc;
|
||||
$body-bg: #f8f7fa;
|
||||
|
||||
body {
|
||||
|
@ -1,83 +0,0 @@
|
||||
/**
|
||||
* Deshabilita o pone en modo de solo lectura los campos del formulario.
|
||||
* @param {string} formSelector - Selector del formulario a deshabilitar.
|
||||
*/
|
||||
const disableStoreForm = (formSelector) => {
|
||||
const form = document.querySelector(formSelector);
|
||||
if (!form) {
|
||||
console.warn(`Formulario no encontrado con el selector: ${formSelector}`);
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Habilita o deshabilita un select con Select2.
|
||||
* @param {HTMLElement} selectElement - El select afectado.
|
||||
* @param {boolean} disabled - Si debe ser deshabilitado.
|
||||
*/
|
||||
const toggleSelect2Disabled = (selectElement, disabled) => {
|
||||
selectElement.disabled = disabled;
|
||||
selectElement.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Aplica modo solo lectura a un select estándar o Select2.
|
||||
* @param {HTMLElement} select - El select a modificar.
|
||||
* @param {boolean} readonly - Si debe estar en modo solo lectura.
|
||||
*/
|
||||
const setSelectReadonly = (select, readonly) => {
|
||||
select.setAttribute('readonly-mode', readonly);
|
||||
select.style.pointerEvents = readonly ? 'none' : '';
|
||||
select.tabIndex = readonly ? -1 : '';
|
||||
|
||||
if (select.classList.contains('select2-hidden-accessible')) {
|
||||
toggleSelect2Disabled(select, readonly);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Aplica modo solo lectura a un checkbox o radio.
|
||||
* @param {HTMLElement} checkbox - El input a modificar.
|
||||
* @param {boolean} readonly - Si debe ser solo lectura.
|
||||
*/
|
||||
const setCheckboxReadonly = (checkbox, readonly) => {
|
||||
checkbox.setAttribute('readonly-mode', readonly);
|
||||
checkbox.style.pointerEvents = readonly ? 'none' : '';
|
||||
checkbox[readonly ? 'addEventListener' : 'removeEventListener']('click', preventInteraction);
|
||||
};
|
||||
|
||||
/**
|
||||
* Previene interacción con el elemento.
|
||||
* @param {Event} event - El evento de clic.
|
||||
*/
|
||||
const preventInteraction = (event) => event.preventDefault();
|
||||
|
||||
// Obtener todos los inputs del formulario
|
||||
const inputs = form.querySelectorAll('input, textarea, select');
|
||||
|
||||
inputs.forEach((el) => {
|
||||
if (!el.classList.contains('data-always-enabled')) {
|
||||
switch (el.tagName) {
|
||||
case 'SELECT':
|
||||
if (el.classList.contains('select2')) {
|
||||
toggleSelect2Disabled(el, true);
|
||||
} else {
|
||||
setSelectReadonly(el, true);
|
||||
}
|
||||
break;
|
||||
case 'INPUT':
|
||||
if (['checkbox', 'radio'].includes(el.type)) {
|
||||
setCheckboxReadonly(el, true);
|
||||
} else {
|
||||
el.readOnly = true;
|
||||
}
|
||||
break;
|
||||
case 'TEXTAREA':
|
||||
el.readOnly = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Hacer la función accesible globalmente
|
||||
window.disableStoreForm = disableStoreForm;
|
@ -4,24 +4,3 @@ import FormCustomListener from '../../assets/js/forms/formCustomListener';
|
||||
new FormCustomListener({
|
||||
buttonSelectors: ['.btn-save', '.btn-cancel', '.btn-reset'] // Selectores para botones
|
||||
});
|
||||
|
||||
Livewire.on('clearLocalStoregeTemplateCustomizer', event => {
|
||||
const _deleteCookie = name => {
|
||||
document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
||||
};
|
||||
|
||||
const pattern = 'templateCustomizer-';
|
||||
|
||||
// Iterar sobre todas las claves en localStorage
|
||||
Object.keys(localStorage).forEach(key => {
|
||||
if (key.startsWith(pattern)) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
});
|
||||
|
||||
_deleteCookie('admin-mode');
|
||||
_deleteCookie('admin-colorPref');
|
||||
_deleteCookie('colorPref');
|
||||
_deleteCookie('theme');
|
||||
_deleteCookie('direction');
|
||||
});
|
||||
|
@ -7,7 +7,7 @@ window.senderResponseForm = new SenderResponseForm();
|
||||
|
||||
Livewire.hook('morphed', ({ component }) => {
|
||||
switch (component.name) {
|
||||
case 'mail-smtp-settings':
|
||||
case 'sendmail-settings':
|
||||
if (window.smtpSettingsForm) {
|
||||
window.smtpSettingsForm.reload(); // Recarga el formulario sin destruir la instancia
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
export default class SmtpSettingsForm {
|
||||
constructor(config = {}) {
|
||||
const defaultConfig = {
|
||||
formSmtpSettingsSelector: '#mail-smtp-settings-card',
|
||||
formSmtpSettingsSelector: '#sendmail-settings-card',
|
||||
changeSmtpSettingsId: 'change_smtp_settings',
|
||||
testSmtpConnectionButtonId: 'test_smtp_connection_button',
|
||||
saveSmtpConnectionButtonId: 'save_smtp_connection_button',
|
||||
|
@ -2,6 +2,10 @@
|
||||
@import 'tailwindcss/components';
|
||||
@import 'tailwindcss/utilities';
|
||||
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.menu-horizontal-wrapper > .menu-inner > .menu-item:last-child {
|
||||
padding-right: 70px;
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ $authentication-1-inner-max-width: 460px !default;
|
||||
position: absolute;
|
||||
top: -35px;
|
||||
left: -45px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='239' height='234' viewBox='0 0 239 234' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='88.5605' y='0.700195' width='149' height='149' rx='19.5' stroke='%237367F0' stroke-opacity='0.16'/%3E%3Crect x='0.621094' y='33.761' width='200' height='200' rx='10' fill='%237367F0' fill-opacity='0.08'/%3E%3C/svg%3E%0A");
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='239' height='234' viewBox='0 0 239 234' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='88.5605' y='0.700195' width='149' height='149' rx='19.5' stroke='%23155dfc' stroke-opacity='0.16'/%3E%3Crect x='0.621094' y='33.761' width='200' height='200' rx='10' fill='%23155dfc' fill-opacity='0.08'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
&:after {
|
||||
@include light.media-breakpoint-down(sm) {
|
||||
@ -98,7 +98,7 @@ $authentication-1-inner-max-width: 460px !default;
|
||||
z-index: -1;
|
||||
bottom: -30px;
|
||||
right: -56px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='181' height='181' viewBox='0 0 181 181' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.30469' y='1.44312' width='178' height='178' rx='19' stroke='%237367F0' stroke-opacity='0.16' stroke-width='2' stroke-dasharray='8 8'/%3E%3Crect x='22.8047' y='22.9431' width='135' height='135' rx='10' fill='%237367F0' fill-opacity='0.08'/%3E%3C/svg%3E");
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='181' height='181' viewBox='0 0 181 181' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.30469' y='1.44312' width='178' height='178' rx='19' stroke='%23155dfc' stroke-opacity='0.16' stroke-width='2' stroke-dasharray='8 8'/%3E%3Crect x='22.8047' y='22.9431' width='135' height='135' rx='10' fill='%23155dfc' fill-opacity='0.08'/%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,30 +0,0 @@
|
||||
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||
|
||||
@section('title', 'Ajustes generales')
|
||||
|
||||
@push('page-script')
|
||||
@vite('vendor/koneko/laravel-vuexy-admin/resources/js/pages/admin-settings-scripts.js')
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-lg-4">
|
||||
<!-- App Settings Card -->
|
||||
<div class="mb-4">
|
||||
@livewire('application-settings')
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<!-- General Settings Card -->
|
||||
<div class="mb-4">
|
||||
@livewire('general-settings')
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<!-- Interface Settings Card -->
|
||||
<div class="mb-4">
|
||||
@livewire('interface-settings')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -10,28 +10,28 @@
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="mb-6">
|
||||
@livewire('cache-stats')
|
||||
@livewire('vuexy-admin::cache-stats')
|
||||
</div>
|
||||
<div class="mb-6">
|
||||
@livewire('session-stats')
|
||||
@livewire('vuexy-admin::session-stats')
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="mb-6">
|
||||
@livewire('cache-functions')
|
||||
@livewire('vuexy-admin::cache-functions')
|
||||
</div>
|
||||
<div class="row">
|
||||
@if($configCache['redisInUse'])
|
||||
<div class="col-md-6">
|
||||
<div class="mb-6">
|
||||
@livewire('redis-stats')
|
||||
@livewire('vuexy-admin::redis-stats')
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if($configCache['memcachedInUse'])
|
||||
<div class="col-md-6">
|
||||
<div class="mb-6">
|
||||
@livewire('memcached-stats')
|
||||
@livewire('vuexy-admin::memcached-stats')
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
24
resources/views/components/alert/basic.blade.php
Normal file
24
resources/views/components/alert/basic.blade.php
Normal file
@ -0,0 +1,24 @@
|
||||
@props([
|
||||
'type' => 'primary', // Tipos: primary, secondary, success, danger, warning, info, dark
|
||||
'outline' => false,
|
||||
'dismissible' => false,
|
||||
'icon' => null,
|
||||
])
|
||||
|
||||
@php
|
||||
$alertClass = $outline ? "alert-outline-{$type}" : "alert-{$type}";
|
||||
@endphp
|
||||
|
||||
<div class="alert {{ $alertClass }} {{ $dismissible ? 'alert-dismissible' : '' }} d-flex align-items-center" role="alert">
|
||||
@if ($icon)
|
||||
<span class="alert-icon rounded me-2">
|
||||
<i class="{{ $icon }}"></i>
|
||||
</span>
|
||||
@endif
|
||||
<div>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
@if ($dismissible)
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
@endif
|
||||
</div>
|
@ -71,7 +71,7 @@
|
||||
@endphp
|
||||
|
||||
{{-- ============================ CHECKBOX CON INPUT GROUP ============================ --}}
|
||||
<div class="mb-4 {{ $parentClass }}">
|
||||
<div class="mb-4 {{ $parentClass }} fv-row">
|
||||
@if ($label)
|
||||
<label for="{{ $checkboxId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
||||
@endif
|
||||
|
@ -74,7 +74,7 @@
|
||||
|
||||
@if ($switch)
|
||||
{{-- ============================ MODO SWITCH ============================ --}}
|
||||
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-2' }}">
|
||||
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-2' }} fv-row">
|
||||
<label class="switch {{ $switchTypeClass }} {{ $switchColorClass }} {{ $sizeClass }} {{ $labelClass }}">
|
||||
<input
|
||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
@ -110,7 +110,7 @@
|
||||
|
||||
@else
|
||||
{{-- ============================ MODO CHECKBOX ============================ --}}
|
||||
<div class="form-check {{ $checkColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }}">
|
||||
<div class="form-check {{ $checkColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }} fv-row">
|
||||
<input
|
||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
|
@ -24,7 +24,7 @@
|
||||
: ($image ? "<img src='{$image}' alt='{$title}' class='img-fluid rounded'>" : '');
|
||||
@endphp
|
||||
|
||||
<div class="mb-4 form-check custom-option custom-option-icon {{ $checked ? 'checked' : '' }}">
|
||||
<div class="mb-4 form-check custom-option custom-option-icon {{ $checked ? 'checked' : '' }} fv-row">
|
||||
<label class="form-check-label custom-option-content" for="{{ $inputId }}">
|
||||
<span class="custom-option-body">
|
||||
{!! $visualContent !!}
|
||||
|
@ -8,6 +8,8 @@
|
||||
'wireSubmit' => false, // Usar wire:submit.prevent
|
||||
'class' => '', // Clases adicionales para el formulario
|
||||
'actionPosition' => 'bottom', // Posición de acciones: top, bottom, both, none
|
||||
'whitOutId' => false, // Excluir el ID del formulario
|
||||
'whitOutMode' => false, // Excluir el modo del formulario
|
||||
])
|
||||
|
||||
@php
|
||||
@ -28,8 +30,12 @@
|
||||
@endphp
|
||||
|
||||
<form {{ $attributes->merge($formAttributes) }}>
|
||||
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="id" />
|
||||
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="mode" />
|
||||
@if (!$whitOutId)
|
||||
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="id" />
|
||||
@endif
|
||||
@if (!$whitOutMode)
|
||||
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="mode" />
|
||||
@endif
|
||||
@if ($mode !== 'delete' && in_array($actionPosition, ['top', 'both']))
|
||||
<div class="notification-container mb-4"></div>
|
||||
<div class="form-actions mb-4">
|
||||
|
@ -1,184 +0,0 @@
|
||||
@props([
|
||||
'uid' => uniqid(),
|
||||
|
||||
|
||||
'model' => '',
|
||||
|
||||
|
||||
'label' => '',
|
||||
'labelClass' => '',
|
||||
|
||||
|
||||
'class' => '',
|
||||
|
||||
'align' => 'start',
|
||||
'size' => '',
|
||||
'mb-0' => false,
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
'parentClass' => '',
|
||||
|
||||
|
||||
'prefix' => null,
|
||||
'suffix' => null,
|
||||
|
||||
'icon' => null,
|
||||
'clickableIcon' => null,
|
||||
|
||||
'inline' => false,
|
||||
|
||||
'labelCol' => 4,
|
||||
'inputCol' => 8,
|
||||
|
||||
'floatLabel' => false,
|
||||
'helperText' => '',
|
||||
|
||||
'attributes' => new \Illuminate\View\ComponentAttributeBag([]), // Atributos adicionales
|
||||
])
|
||||
|
||||
@php
|
||||
// Configuración dinámica de atributos y clases CSS
|
||||
$livewireModel = $attributes->get('wire:model', $model); // Permitir uso directo de wire:model en el atributo
|
||||
$name = $name ?: $livewireModel; // Si no se proporciona el nombre, toma el nombre del modelo
|
||||
$inputId = $id ?: ($uid ? $name . '_' . $uid : $name); // ID generado si no se proporciona uno
|
||||
|
||||
|
||||
// Obtener los atributos actuales en un array
|
||||
$attributesArray = array_merge([
|
||||
'type' => $type,
|
||||
'id' => $inputId,
|
||||
'name' => $name,
|
||||
]);
|
||||
|
||||
|
||||
// Agregar wire:model solo si existe
|
||||
if ($livewireModel) {
|
||||
$attributesArray['wire:model'] = $livewireModel;
|
||||
}
|
||||
|
||||
|
||||
$attributesArray = array_merge($attributesArray, $attributes->getAttributes());
|
||||
|
||||
|
||||
// Reconstruir el ComponentAttributeBag con los atributos modificados
|
||||
$inputAttributes = new \Illuminate\View\ComponentAttributeBag($attributesArray);
|
||||
|
||||
dump($inputAttributes);
|
||||
|
||||
|
||||
// Manejo de errores de validación
|
||||
$errorKey = $livewireModel ?: $name;
|
||||
$errorClass = $errors->has($errorKey) ? 'is-invalid' : '';
|
||||
|
||||
// Definir el tamaño del input basado en la clase seleccionada
|
||||
$sizeClass = $size === 'small' ? 'form-control-sm' : ($size === 'large' ? 'form-control-lg' : '');
|
||||
|
||||
// Alineación del texto
|
||||
$alignClass = match ($align) {
|
||||
'center' => 'text-center',
|
||||
'end' => 'text-end',
|
||||
default => ''
|
||||
};
|
||||
|
||||
// Clases combinadas para el input
|
||||
$fullClass = trim("form-control $sizeClass $alignClass $errorClass $class");
|
||||
|
||||
// Detectar si se necesita usar input-group
|
||||
$requiresInputGroup = $prefix || $suffix || $icon || $clickableIcon;
|
||||
@endphp
|
||||
|
||||
{{-- Input oculto sin estilos --}}
|
||||
@if($type === 'hidden')
|
||||
<input type="hidden" id="{{ $inputId }}" name="{{ $name }}" {{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
/>
|
||||
@elseif($floatLabel)
|
||||
{{-- Input con etiqueta flotante --}}
|
||||
<div class="form-floating mb-4 {{ $parentClass }}">
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
id="{{ $inputId }}"
|
||||
name="{{ $name }}"
|
||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
class="{{ $fullClass }}"
|
||||
placeholder="{{ $placeholder ?: 'Ingrese ' . strtolower($label) }}"
|
||||
{{ $step ? "step=$step" : '' }}
|
||||
{{ $max ? "maxlength=$max" : '' }}
|
||||
{{ $min ? "minlength=$min" : '' }}
|
||||
{{ $pattern ? "pattern=$pattern" : '' }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
{{ $multiple ? 'multiple' : '' }} />
|
||||
<label for="{{ $inputId }}">{{ $label }}</label>
|
||||
|
||||
@if ($helperText)
|
||||
<div class="form-text">{{ $helperText }}</div>
|
||||
@endif
|
||||
|
||||
@if ($errors->has($errorKey))
|
||||
<span class="text-danger">{{ $errors->first($errorKey) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
{{-- Input con formato clásico --}}
|
||||
<div class="{{ $inline ? 'row' : '' }} {{ $parentClass }} fv-row mb-4">
|
||||
@isset($label)
|
||||
<label for="{{ $inputId }}" class="{{ $inline ? 'col-form-label col-md-' . $labelCol : 'form-label' }} {{ $labelClass }}">{{ $label }}</label>
|
||||
@endisset
|
||||
<div class="{{ $inline ? 'col-md-' . $inputCol : '' }}">
|
||||
@if ($requiresInputGroup)
|
||||
<div class="input-group input-group-merge">
|
||||
@if ($prefix)
|
||||
<span class="input-group-text">{{ $prefix }}</span>
|
||||
@endif
|
||||
@if ($icon)
|
||||
<span class="input-group-text"><i class="{{ $icon }}"></i></span>
|
||||
@endif
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
id="{{ $inputId }}"
|
||||
name="{{ $name }}"
|
||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
class="{{ $fullClass }}"
|
||||
placeholder="{{ $placeholder ?: 'Ingrese ' . strtolower($label) }}"
|
||||
{{ $step ? "step=$step" : '' }}
|
||||
{{ $max ? "maxlength=$max" : '' }}
|
||||
{{ $min ? "minlength=$min" : '' }}
|
||||
{{ $pattern ? "pattern=$pattern" : '' }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
{{ $multiple ? 'multiple' : '' }} />
|
||||
@if ($suffix)
|
||||
<span class="input-group-text">{{ $suffix }}</span>
|
||||
@endif
|
||||
@if ($clickableIcon)
|
||||
<button type="button" class="input-group-text cursor-pointer">
|
||||
<i class="{{ $clickableIcon }}"></i>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
{{-- Input sin prefijo o íconos --}}
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
id="{{ $inputId }}"
|
||||
name="{{ $name }}"
|
||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
class="{{ $fullClass }}"
|
||||
placeholder="{{ $placeholder ?: 'Ingrese ' . strtolower($label) }}"
|
||||
{{ $step ? "step=$step" : '' }}
|
||||
{{ $max ? "maxlength=$max" : '' }}
|
||||
{{ $min ? "minlength=$min" : '' }}
|
||||
{{ $pattern ? "pattern=$pattern" : '' }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
{{ $multiple ? 'multiple' : '' }} />
|
||||
@endif
|
||||
@if ($helperText)
|
||||
<small class="form-text text-muted">{{ $helperText }}</small>
|
||||
@endif
|
||||
@if ($errors->has($errorKey))
|
||||
<span class="text-danger">{{ $errors->first($errorKey) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
@ -16,13 +16,18 @@
|
||||
'mb0' => false, // Remover margen inferior
|
||||
'parentClass' => '',
|
||||
|
||||
// Elementos opcionales antes/después del input
|
||||
// Elementos de prefijo
|
||||
'prefix' => null,
|
||||
'suffix' => null,
|
||||
'prefixIcon' => null,
|
||||
'icon' => null, // Alias para prefixIcon
|
||||
'prefixClickable' => false,
|
||||
'prefixAction' => null,
|
||||
|
||||
// Íconos dentro del input
|
||||
'icon' => null,
|
||||
'clickableIcon' => null,
|
||||
// Elementos de sufijo
|
||||
'suffix' => null,
|
||||
'suffixIcon' => null,
|
||||
'suffixClickable' => false,
|
||||
'suffixAction' => null,
|
||||
|
||||
// Configuración especial
|
||||
'phoneMode' => false, // "national", "international", "both"
|
||||
@ -47,6 +52,9 @@
|
||||
$inputId = $attributes->get('id', $name . '_' . $uid);
|
||||
$type = $attributes->get('type', 'text');
|
||||
|
||||
// Manejar el alias de icon a prefixIcon
|
||||
$prefixIcon = $prefixIcon ?? $icon;
|
||||
|
||||
// **Definir formato de teléfono según `phoneMode`**
|
||||
if ($phoneMode) {
|
||||
$type = 'tel';
|
||||
@ -120,37 +128,68 @@
|
||||
'id' => $inputId,
|
||||
'name' => $name,
|
||||
])->class("form-control $sizeClass $alignClass $errorClass");
|
||||
|
||||
// Verificar si se necesita el input-group
|
||||
$hasAddons = $prefix || $prefixIcon || $suffix || $suffixIcon;
|
||||
@endphp
|
||||
|
||||
{{-- Estructura del Input --}}
|
||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }}">
|
||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} fv-row">
|
||||
{{-- Etiqueta --}}
|
||||
@if ($label)
|
||||
<label for="{{ $inputId }}" class="form-label {{ $labelClass }}">{{ $label }}</label>
|
||||
@endif
|
||||
|
||||
{{-- Input con Prefijos, Sufijos o Íconos --}}
|
||||
@if ($prefix || $suffix || $icon || $clickableIcon)
|
||||
{{-- Input con Prefijos o Sufijos --}}
|
||||
@if ($hasAddons)
|
||||
<div class="input-group input-group-merge">
|
||||
@isset($prefix)
|
||||
<span class="input-group-text">{{ $prefix }}</span>
|
||||
@endisset
|
||||
|
||||
@isset($icon)
|
||||
<span class="input-group-text"><i class="{{ $icon }}"></i></span>
|
||||
@endisset
|
||||
{{-- Prefijo --}}
|
||||
@if ($prefix || $prefixIcon)
|
||||
@if ($prefixClickable)
|
||||
<button type="button" class="input-group-text cursor-pointer" {{ $prefixAction ? "wire:click=$prefixAction" : '' }}>
|
||||
@if ($prefixIcon)
|
||||
<i class="{{ $prefixIcon }}"></i>
|
||||
@endif
|
||||
@if ($prefix)
|
||||
{{ $prefix }}
|
||||
@endif
|
||||
</button>
|
||||
@else
|
||||
<span class="input-group-text">
|
||||
@if ($prefixIcon)
|
||||
<i class="{{ $prefixIcon }}"></i>
|
||||
@endif
|
||||
@if ($prefix)
|
||||
{{ $prefix }}
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<input {!! $inputAttributes !!} {{ $livewireModel ? "wire:model=$livewireModel" : '' }} />
|
||||
|
||||
@isset($suffix)
|
||||
<span class="input-group-text">{{ $suffix }}</span>
|
||||
@endisset
|
||||
|
||||
@isset($clickableIcon)
|
||||
<button type="button" class="input-group-text cursor-pointer">
|
||||
<i class="{{ $clickableIcon }}"></i>
|
||||
</button>
|
||||
@endisset
|
||||
{{-- Sufijo --}}
|
||||
@if ($suffix || $suffixIcon)
|
||||
@if ($suffixClickable)
|
||||
<button type="button" class="input-group-text cursor-pointer" {{ $suffixAction ? "wire:click=$suffixAction" : '' }}>
|
||||
@if ($suffixIcon)
|
||||
<i class="{{ $suffixIcon }}"></i>
|
||||
@endif
|
||||
@if ($suffix)
|
||||
{{ $suffix }}
|
||||
@endif
|
||||
</button>
|
||||
@else
|
||||
<span class="input-group-text">
|
||||
@if ($suffixIcon)
|
||||
<i class="{{ $suffixIcon }}"></i>
|
||||
@endif
|
||||
@if ($suffix)
|
||||
{{ $suffix }}
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
{{-- Input Simple --}}
|
||||
|
@ -72,7 +72,7 @@
|
||||
@endphp
|
||||
|
||||
{{-- ============================ RADIO BUTTON CON INPUT GROUP ============================ --}}
|
||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }}">
|
||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} fv-row">
|
||||
@if ($label)
|
||||
<label for="{{ $radioId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
||||
@endif
|
||||
@ -86,7 +86,7 @@
|
||||
type="radio"
|
||||
{{ $livewireRadio }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
class="form-check-input mt-0"
|
||||
class="form-check-input fv-row mt-0"
|
||||
onchange="toggleRadioInputState('{{ $radioId }}', '{{ $textInputId }}', {{ $focusOnCheck ? 'true' : 'false' }}, {{ $disableOnOffRadio ? 'true' : 'false' }})"
|
||||
>
|
||||
</div>
|
||||
|
@ -72,7 +72,7 @@
|
||||
|
||||
@if ($switch)
|
||||
{{-- ============================ MODO SWITCH ============================ --}}
|
||||
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }}">
|
||||
<div class="{{ $alignClass }} {{ $inline ? 'd-inline-block' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }} fv-row">
|
||||
<label class="switch {{ $switchTypeClass }} {{ $switchColorClass }} {{ $sizeClass }} {{ $labelClass }}">
|
||||
<input
|
||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
@ -102,7 +102,7 @@
|
||||
|
||||
@else
|
||||
{{-- ============================ MODO RADIO ============================ --}}
|
||||
<div class="{{ $layoutClass }} {{ $radioColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }}">
|
||||
<div class="{{ $layoutClass }} {{ $radioColorClass }} {{ $alignClass }} {{ $sizeClass }} {{ $inline ? 'form-check-inline' : '' }} {{ $parentClass }} {{ $mb0 ? '' : 'mb-4' }} fv-row">
|
||||
<input
|
||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
|
@ -1,86 +0,0 @@
|
||||
@props([
|
||||
'uid' => uniqid(),
|
||||
'id' => '',
|
||||
'model' => '',
|
||||
'name' => '',
|
||||
'label' => '',
|
||||
'labelClass' => 'form-label',
|
||||
'placeholder' => '',
|
||||
'options' => [],
|
||||
'selected' => null,
|
||||
'class' => '',
|
||||
'parentClass' => '',
|
||||
'multiple' => false,
|
||||
'disabled' => false,
|
||||
'prefixLabel' => null,
|
||||
'suffixLabel' => null,
|
||||
'buttonBefore' => null,
|
||||
'buttonAfter' => null,
|
||||
'inline' => false, // Si es en línea
|
||||
'labelCol' => 2, // Columnas que ocupa el label (Bootstrap grid)
|
||||
'inputCol' => 10, // Columnas que ocupa el input (Bootstrap grid)
|
||||
'helperText' => '', // Texto de ayuda opcional
|
||||
'select2' => false, // Activar Select2 automáticamente
|
||||
])
|
||||
|
||||
@php
|
||||
$name = $name ?: $model;
|
||||
$inputId = $id ?: ($uid ? str_replace('.', '_', $name) . '_' . $uid : $name);
|
||||
$placeholder = $placeholder ?: 'Seleccione ' . strtolower($label);
|
||||
$errorClass = $errors->has($model) ? 'is-invalid' : '';
|
||||
$options = is_array($options) ? collect($options) : $options;
|
||||
$select2Class = $select2 ? 'select2' : ''; // Agrega la clase select2 si está habilitado
|
||||
@endphp
|
||||
|
||||
<div class="{{ $inline ? 'row' : 'mb-4' }} {{ $parentClass }} fv-row">
|
||||
@if($label != null)
|
||||
<label for="{{ $inputId }}" class="{{ $inline ? 'col-md-' . $labelCol : '' }} {{ $labelClass }}">{{ $label }}</label>
|
||||
@endif
|
||||
|
||||
<div class="{{ $inline ? 'col-md-' . $inputCol : '' }}">
|
||||
<div class="input-group {{ $prefixLabel || $suffixLabel || $buttonBefore || $buttonAfter ? 'input-group-merge' : '' }}">
|
||||
@if ($buttonBefore)
|
||||
<button class="btn btn-outline-primary waves-effect" type="button">{{ $buttonBefore }}</button>
|
||||
@endif
|
||||
|
||||
@if ($prefixLabel)
|
||||
<label class="input-group-text" for="{{ $inputId }}">{{ $prefixLabel }}</label>
|
||||
@endif
|
||||
|
||||
<select
|
||||
id="{{ $inputId }}"
|
||||
name="{{ $name }}"
|
||||
class="form-select {{ $errorClass }} {{ $class }} {{ $select2Class }}"
|
||||
{{ $multiple ? 'multiple' : '' }}
|
||||
{{ $disabled ? 'disabled' : '' }}
|
||||
{{ $model ? "wire:model=$model" : '' }}
|
||||
{{ $select2 ? 'data-live-search="true"' : '' }}
|
||||
>
|
||||
@if (!$multiple && $placeholder)
|
||||
<option value="">{{ $placeholder }}</option>
|
||||
@endif
|
||||
@foreach ($options as $key => $value)
|
||||
<option value="{{ $key }}" {{ (string) $key === (string) $selected ? 'selected' : '' }}>
|
||||
{{ $value }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
@if ($suffixLabel)
|
||||
<label class="input-group-text" for="{{ $inputId }}">{{ $suffixLabel }}</label>
|
||||
@endif
|
||||
|
||||
@if ($buttonAfter)
|
||||
<button class="btn btn-outline-primary waves-effect" type="button">{{ $buttonAfter }}</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($helperText)
|
||||
<div class="form-text">{{ $helperText }}</div>
|
||||
@endif
|
||||
|
||||
@if ($errors->has($model))
|
||||
<span class="text-danger">{{ $errors->first($model) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
@ -88,7 +88,7 @@
|
||||
@endphp
|
||||
|
||||
{{-- ============================ TEXTAREA ============================ --}}
|
||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} {{ $alignClass }} {{ $floatingClass }}">
|
||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} {{ $alignClass }} {{ $floatingClass }} fv-row">
|
||||
@if (!$floating && $label)
|
||||
<label for="{{ $inputId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
||||
@endif
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user