Prepare modules
This commit is contained in:
parent
099267ee07
commit
7d8566350d
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@
|
|||||||
/.phpunit.result.cache
|
/.phpunit.result.cache
|
||||||
/.zed
|
/.zed
|
||||||
/.idea
|
/.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
|
class CacheController extends Controller
|
||||||
{
|
{
|
||||||
|
public function index(CacheConfigService $cacheConfigService)
|
||||||
|
{
|
||||||
|
$configCache = $cacheConfigService->getConfig();
|
||||||
|
|
||||||
|
return view('vuexy-admin::cache-manager.index', compact('configCache'));
|
||||||
|
}
|
||||||
|
|
||||||
public function generateConfigCache()
|
public function generateConfigCache()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -27,15 +34,9 @@ class CacheController extends Controller
|
|||||||
Artisan::call('route:cache');
|
Artisan::call('route:cache');
|
||||||
|
|
||||||
return response()->json(['success' => true, 'message' => 'Cache de rutas generado correctamente.']);
|
return response()->json(['success' => true, 'message' => 'Cache de rutas generado correctamente.']);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return response()->json(['success' => false, 'message' => 'Error al generar el cache de rutas.', 'error' => $e->getMessage()], 500);
|
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;
|
namespace Koneko\VuexyAdmin\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Arr;
|
|
||||||
use Spatie\Permission\Models\Permission;
|
|
||||||
use Yajra\DataTables\Facades\DataTables;
|
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use Koneko\VuexyAdmin\Queries\GenericQueryBuilder;
|
||||||
|
|
||||||
class PermissionController extends Controller
|
class PermissionController extends Controller
|
||||||
{
|
{
|
||||||
@ -19,17 +16,26 @@ class PermissionController extends Controller
|
|||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
if ($request->ajax()) {
|
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)
|
return (new GenericQueryBuilder($request, $bootstrapTableIndexConfig))->getJson();
|
||||||
->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 view('vuexy-admin::permissions.index');
|
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 App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
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\Models\User;
|
||||||
use Koneko\VuexyAdmin\Services\AvatarImageService;
|
use Koneko\VuexyAdmin\Services\AvatarImageService;
|
||||||
use Koneko\VuexyAdmin\Queries\GenericQueryBuilder;
|
use Koneko\VuexyAdmin\Queries\GenericQueryBuilder;
|
||||||
@ -24,97 +24,19 @@ class UserController extends Controller
|
|||||||
'table' => 'users',
|
'table' => 'users',
|
||||||
'columns' => [
|
'columns' => [
|
||||||
'users.id',
|
'users.id',
|
||||||
'users.code',
|
'users.name AS full_name',
|
||||||
DB::raw("CONCAT_WS(' ', users.name, users.last_name) AS full_name"),
|
|
||||||
'users.email',
|
'users.email',
|
||||||
'users.birth_date',
|
'users.email_verified_at',
|
||||||
'users.hire_date',
|
|
||||||
'users.curp',
|
|
||||||
'users.nss',
|
|
||||||
'users.job_title',
|
|
||||||
'users.profile_photo_path',
|
'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',
|
'users.status',
|
||||||
DB::raw("CONCAT_WS(' ', created.name, created.last_name) AS creator"),
|
'users.created_by',
|
||||||
'created.email AS creator_email',
|
|
||||||
'users.created_at',
|
'users.created_at',
|
||||||
'users.updated_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' => [
|
'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',
|
'default_sort_order' => 'asc',
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -160,9 +82,7 @@ class UserController extends Controller
|
|||||||
//$user->stores()->attach($request->stores);
|
//$user->stores()->attach($request->stores);
|
||||||
|
|
||||||
if ($request->file('photo')){
|
if ($request->file('photo')){
|
||||||
$avatarImageService = new AvatarImageService();
|
app(AvatarImageService::class)->updateProfilePhoto($user, $request->file('photo'));
|
||||||
|
|
||||||
$avatarImageService->updateProfilePhoto($user, $request->file('photo'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(['success' => 'Se agrego correctamente el usuario']);
|
return response()->json(['success' => 'Se agrego correctamente el usuario']);
|
||||||
@ -217,10 +137,9 @@ class UserController extends Controller
|
|||||||
//$user->stores()->sync($request->stores);
|
//$user->stores()->sync($request->stores);
|
||||||
|
|
||||||
// Actualizamos foto de perfil
|
// Actualizamos foto de perfil
|
||||||
if ($request->file('photo'))
|
if ($request->file('photo')){
|
||||||
$avatarImageService = new AvatarImageService();
|
app(AvatarImageService::class)->updateProfilePhoto($user, $request->file('photo'));
|
||||||
|
}
|
||||||
$avatarImageService->updateProfilePhoto($user, $request->file('photo'));
|
|
||||||
|
|
||||||
return response()->json(['success' => 'Se guardo correctamente los cambios.']);
|
return response()->json(['success' => 'Se guardo correctamente los cambios.']);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ namespace Koneko\VuexyAdmin\Http\Controllers;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Koneko\VuexyAdmin\Services\AvatarInitialsService;
|
use Koneko\VuexyAdmin\Services\AvatarInitialsService;
|
||||||
use Koneko\VuexyAdmin\Models\User;
|
|
||||||
|
|
||||||
class UserProfileController extends Controller
|
class UserProfileController extends Controller
|
||||||
{
|
{
|
||||||
@ -35,10 +34,8 @@ class UserProfileController extends Controller
|
|||||||
$background = $request->get('background', 'EBF4FF');
|
$background = $request->get('background', 'EBF4FF');
|
||||||
$size = $request->get('size', 100);
|
$size = $request->get('size', 100);
|
||||||
|
|
||||||
$avatarService = new AvatarInitialsService();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return $avatarService->getAvatarImage($name, $color, $background, $size);
|
return app(AvatarInitialsService::class)->getAvatarImage($name, $color, $background, $size);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// String base64 de una imagen PNG transparente de 1x1 píxel
|
// 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;
|
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.
|
* 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;
|
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.
|
* Retorna la ruta de la vista asociada al formulario.
|
||||||
*
|
*
|
||||||
@ -171,6 +143,50 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
|||||||
*/
|
*/
|
||||||
abstract protected function viewPath(): string;
|
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 =====================
|
// ===================== VALIDACIONES =====================
|
||||||
|
|
||||||
protected function attributes(): array
|
protected function attributes(): array
|
||||||
@ -198,7 +214,7 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
|||||||
|
|
||||||
$model = new ($this->model());
|
$model = new ($this->model());
|
||||||
|
|
||||||
$this->tagName = $model->tagName;
|
$this->tagName = Str::camel($model->tagName);
|
||||||
$this->columnNameLabel = $model->columnNameLabel;
|
$this->columnNameLabel = $model->columnNameLabel;
|
||||||
$this->singularName = $model->singularName;
|
$this->singularName = $model->singularName;
|
||||||
$this->offcanvasId = 'offcanvas' . ucfirst(Str::camel($model->tagName));
|
$this->offcanvasId = 'offcanvas' . ucfirst(Str::camel($model->tagName));
|
||||||
@ -288,6 +304,9 @@ abstract class AbstractFormOffCanvasComponent extends Component
|
|||||||
$model = $this->model()::find($id);
|
$model = $this->model()::find($id);
|
||||||
|
|
||||||
if ($model) {
|
if ($model) {
|
||||||
|
|
||||||
|
dd($this->fields());
|
||||||
|
|
||||||
$data = $model->only(['id', ...$this->fields()]);
|
$data = $model->only(['id', ...$this->fields()]);
|
||||||
|
|
||||||
$this->applyCasts($data);
|
$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\Role;
|
||||||
use Spatie\Permission\Models\Permission;
|
use Spatie\Permission\Models\Permission;
|
||||||
|
|
||||||
class RoleIndex extends Component
|
class RolesIndex extends Component
|
||||||
{
|
{
|
||||||
use WithPagination;
|
use WithPagination;
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ class RoleIndex extends Component
|
|||||||
|
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
return view('livewire.roles', [
|
return view('vuexy-admin::livewire.roles.index', [
|
||||||
'index' => Role::paginate(10)
|
'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;
|
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\Livewire\Form\AbstractFormOffCanvasComponent;
|
||||||
use Koneko\VuexyAdmin\Models\User;
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
use Koneko\VuexyContacts\Services\{ContactCatalogService,ConstanciaFiscalService,FacturaXmlService};
|
|
||||||
use Koneko\VuexyStoreManager\Services\StoreCatalogService;
|
|
||||||
use Livewire\WithFileUploads;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class UserOffCanvasForm
|
* Class UserOffCanvasForm
|
||||||
@ -22,40 +16,15 @@ use Livewire\WithFileUploads;
|
|||||||
*/
|
*/
|
||||||
class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||||
{
|
{
|
||||||
use WithFileUploads;
|
|
||||||
|
|
||||||
public $doc_file;
|
|
||||||
public $dropzoneVisible = true;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Propiedades del formulario relacionadas con el usuario.
|
* Propiedades del formulario relacionadas con el usuario.
|
||||||
*/
|
*/
|
||||||
public $code,
|
public $code,
|
||||||
$parent_id,
|
|
||||||
$name,
|
$name,
|
||||||
$last_name,
|
$last_name,
|
||||||
$email,
|
$email,
|
||||||
$company,
|
|
||||||
$rfc,
|
|
||||||
$nombre_fiscal,
|
|
||||||
$tipo_persona,
|
|
||||||
$c_regimen_fiscal,
|
|
||||||
$domicilio_fiscal,
|
|
||||||
$is_partner,
|
|
||||||
$is_employee,
|
|
||||||
$is_prospect,
|
|
||||||
$is_customer,
|
|
||||||
$is_provider,
|
|
||||||
$status;
|
$status;
|
||||||
|
|
||||||
/**
|
|
||||||
* Listas de opciones para selects en el formulario.
|
|
||||||
*/
|
|
||||||
public $store_options = [],
|
|
||||||
$work_center_options = [],
|
|
||||||
$manager_options = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Eventos de escucha de Livewire.
|
* Eventos de escucha de Livewire.
|
||||||
*
|
*
|
||||||
@ -85,28 +54,6 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
|||||||
return User::class;
|
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.
|
* Campo que se debe enfocar cuando se abra el formulario.
|
||||||
*
|
*
|
||||||
@ -117,6 +64,8 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
|||||||
return 'name';
|
return 'name';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===================== VALIDACIONES =====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define reglas de validación dinámicas basadas en el modo actual.
|
* Define reglas de validación dinámicas basadas en el modo actual.
|
||||||
*
|
*
|
||||||
@ -129,10 +78,8 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
|||||||
case 'create':
|
case 'create':
|
||||||
case 'edit':
|
case 'edit':
|
||||||
return [
|
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'],
|
'name' => ['required', 'string', 'max:96'],
|
||||||
'notes' => ['nullable', 'string', 'max:1024'],
|
|
||||||
'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
case 'delete':
|
case 'delete':
|
||||||
@ -145,8 +92,6 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===================== VALIDACIONES =====================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get custom attributes for validator errors.
|
* Get custom attributes for validator errors.
|
||||||
*
|
*
|
||||||
@ -168,121 +113,10 @@ class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
|||||||
protected function messages(): array
|
protected function messages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'code.unique' => 'Este código ya está en uso por otro usuario.',
|
|
||||||
'name.required' => 'El nombre del usuario es obligatorio.',
|
'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.
|
* Ruta de la vista asociada con este formulario.
|
||||||
*
|
*
|
||||||
|
@ -40,7 +40,6 @@ class UserShow extends Component
|
|||||||
$is_prospect,
|
$is_prospect,
|
||||||
$is_customer,
|
$is_customer,
|
||||||
$is_provider,
|
$is_provider,
|
||||||
$is_user,
|
|
||||||
$status;
|
$status;
|
||||||
public $deleteUserImage;
|
public $deleteUserImage;
|
||||||
public $cuentaUsuarioAlert,
|
public $cuentaUsuarioAlert,
|
||||||
@ -55,7 +54,6 @@ class UserShow extends Component
|
|||||||
'is_prospect' => 'nullable|boolean',
|
'is_prospect' => 'nullable|boolean',
|
||||||
'is_customer' => 'nullable|boolean',
|
'is_customer' => 'nullable|boolean',
|
||||||
'is_provider' => 'nullable|boolean',
|
'is_provider' => 'nullable|boolean',
|
||||||
'is_user' => 'nullable|boolean',
|
|
||||||
'pricelist_id' => 'nullable|integer',
|
'pricelist_id' => 'nullable|integer',
|
||||||
'enable_credit' => 'nullable|boolean',
|
'enable_credit' => 'nullable|boolean',
|
||||||
'credit_days' => 'nullable|integer',
|
'credit_days' => 'nullable|integer',
|
||||||
@ -102,7 +100,6 @@ class UserShow extends Component
|
|||||||
$this->is_prospect = $this->user->is_prospect? true : false;
|
$this->is_prospect = $this->user->is_prospect? true : false;
|
||||||
$this->is_customer = $this->user->is_customer? true : false;
|
$this->is_customer = $this->user->is_customer? true : false;
|
||||||
$this->is_provider = $this->user->is_provider? 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->pricelist_id = $this->user->pricelist_id;
|
||||||
$this->enable_credit = $this->user->enable_credit? true : false;
|
$this->enable_credit = $this->user->enable_credit? true : false;
|
||||||
$this->credit_days = $this->user->credit_days;
|
$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_prospect'] = $validatedData['is_prospect'] ? 1 : 0;
|
||||||
$validatedData['is_customer'] = $validatedData['is_customer'] ? 1 : 0;
|
$validatedData['is_customer'] = $validatedData['is_customer'] ? 1 : 0;
|
||||||
$validatedData['is_provider'] = $validatedData['is_provider'] ? 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['pricelist_id'] = $validatedData['pricelist_id'] ?: null;
|
||||||
$validatedData['enable_credit'] = $validatedData['enable_credit'] ? 1 : 0;
|
$validatedData['enable_credit'] = $validatedData['enable_credit'] ? 1 : 0;
|
||||||
$validatedData['credit_days'] = $validatedData['credit_days'] ?: null;
|
$validatedData['credit_days'] = $validatedData['credit_days'] ?: null;
|
||||||
@ -150,7 +146,6 @@ class UserShow extends Component
|
|||||||
$validatedData['cargo'] = null;
|
$validatedData['cargo'] = null;
|
||||||
$validatedData['is_prospect'] = null;
|
$validatedData['is_prospect'] = null;
|
||||||
$validatedData['is_provider'] = null;
|
$validatedData['is_provider'] = null;
|
||||||
$validatedData['is_user'] = null;
|
|
||||||
$validatedData['enable_credit'] = null;
|
$validatedData['enable_credit'] = null;
|
||||||
$validatedData['credit_days'] = null;
|
$validatedData['credit_days'] = null;
|
||||||
$validatedData['credit_limit'] = null;
|
$validatedData['credit_limit'] = null;
|
||||||
|
@ -6,11 +6,11 @@ use Koneko\VuexyAdmin\Models\User;
|
|||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class UserCount extends Component
|
class UsersCount extends Component
|
||||||
{
|
{
|
||||||
public $total, $enabled, $disabled;
|
public $total, $enabled, $disabled;
|
||||||
|
|
||||||
protected $listeners = ['refreshUserCount' => 'updateCounts'];
|
protected $listeners = ['refreshUsersCount' => 'updateCounts'];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
@ -2,17 +2,11 @@
|
|||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||||
|
|
||||||
use Livewire\WithFileUploads;
|
|
||||||
use Koneko\VuexyAdmin\Models\User;
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
|
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.
|
* Almacena rutas útiles para la funcionalidad de edición o eliminación.
|
||||||
*/
|
*/
|
||||||
@ -33,30 +27,12 @@ class UserIndex extends AbstractIndexComponent
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'action' => 'Acciones',
|
'action' => 'Acciones',
|
||||||
'code' => 'Código personal',
|
'full_name' => 'Nombre completo',
|
||||||
'full_name' => 'Nombre Completo',
|
'email' => 'Correo electrónico',
|
||||||
'email' => 'Correo Electrónico',
|
'email_verified_at' => 'Correo verificado',
|
||||||
'parent_name' => 'Responsable',
|
'created_by' => 'Creado Por',
|
||||||
'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',
|
'status' => 'Estatus',
|
||||||
'creator' => 'Creado Por',
|
'created_at' => 'Fecha Creación',
|
||||||
'creator_email' => 'Correo Creador',
|
|
||||||
'created_at' => 'Fecha de Creación',
|
|
||||||
'updated_at' => 'Última Modificación',
|
'updated_at' => 'Última Modificación',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -86,14 +62,27 @@ class UserIndex extends AbstractIndexComponent
|
|||||||
'formatter' => 'emailFormatter',
|
'formatter' => 'emailFormatter',
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
'parent_name' => [
|
'email_verified_at' => [
|
||||||
'formatter' => 'contactParentFormatter',
|
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
'agent_name' => [
|
'parent_id' => [
|
||||||
'formatter' => 'agentFormatter',
|
'formatter' => 'parentProfileFormatter',
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
|
'agent_id' => [
|
||||||
|
'formatter' => 'agentProfileFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'phone' => [
|
||||||
|
'formatter' => 'telFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'mobile' => [
|
||||||
|
'formatter' => 'telFormatter',
|
||||||
|
],
|
||||||
|
'whatsapp' => [
|
||||||
|
'formatter' => 'whatsappFormatter',
|
||||||
|
],
|
||||||
'company' => [
|
'company' => [
|
||||||
'formatter' => 'textNowrapFormatter',
|
'formatter' => 'textNowrapFormatter',
|
||||||
],
|
],
|
||||||
@ -103,10 +92,16 @@ class UserIndex extends AbstractIndexComponent
|
|||||||
'nss' => [
|
'nss' => [
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
'job_title' => [
|
'license_number' => [
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'job_position' => [
|
||||||
'formatter' => 'textNowrapFormatter',
|
'formatter' => 'textNowrapFormatter',
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
|
'pais' => [
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
'rfc' => [
|
'rfc' => [
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
@ -114,6 +109,7 @@ class UserIndex extends AbstractIndexComponent
|
|||||||
'formatter' => 'textNowrapFormatter',
|
'formatter' => 'textNowrapFormatter',
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
|
|
||||||
'domicilio_fiscal' => [
|
'domicilio_fiscal' => [
|
||||||
'visible' => false,
|
'visible' => false,
|
||||||
],
|
],
|
||||||
@ -176,14 +172,14 @@ class UserIndex extends AbstractIndexComponent
|
|||||||
],
|
],
|
||||||
'align' => 'center',
|
'align' => 'center',
|
||||||
],
|
],
|
||||||
'is_provider' => [
|
'is_supplier' => [
|
||||||
'formatter' => [
|
'formatter' => [
|
||||||
'name' => 'dynamicBooleanFormatter',
|
'name' => 'dynamicBooleanFormatter',
|
||||||
'params' => ['tag' => 'checkSI'],
|
'params' => ['tag' => 'checkSI'],
|
||||||
],
|
],
|
||||||
'align' => 'center',
|
'align' => 'center',
|
||||||
],
|
],
|
||||||
'is_user' => [
|
'is_carrier' => [
|
||||||
'formatter' => [
|
'formatter' => [
|
||||||
'name' => 'dynamicBooleanFormatter',
|
'name' => 'dynamicBooleanFormatter',
|
||||||
'params' => ['tag' => 'checkSI'],
|
'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.
|
* 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
|
<?php
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Livewire\WithFileUploads;
|
use Livewire\WithFileUploads;
|
||||||
use Koneko\VuexyAdmin\Services\AdminSettingsService;
|
use Koneko\VuexyAdmin\Services\AdminSettingsService;
|
||||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||||
|
|
||||||
class GeneralSettings extends Component
|
class AppFaviconSettings extends Component
|
||||||
{
|
{
|
||||||
use WithFileUploads;
|
use WithFileUploads;
|
||||||
|
|
||||||
private $targetNotify = "#general-settings-card .notification-container";
|
private $targetNotify = "#app-favicon-settings-card .notification-container";
|
||||||
|
|
||||||
public $admin_title;
|
|
||||||
public $admin_favicon_16x16,
|
public $admin_favicon_16x16,
|
||||||
$admin_favicon_76x76,
|
$admin_favicon_76x76,
|
||||||
$admin_favicon_120x120,
|
$admin_favicon_120x120,
|
||||||
@ -25,50 +24,25 @@ class GeneralSettings extends Component
|
|||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->loadSettings();
|
$this->resetForm();
|
||||||
}
|
|
||||||
|
|
||||||
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'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save()
|
public function save()
|
||||||
{
|
{
|
||||||
$this->validate([
|
$this->validate([
|
||||||
'admin_title' => 'required|string|max:255',
|
'upload_image_favicon' => 'required|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||||
'upload_image_favicon' => 'nullable|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
|
// Limpiar cache de plantilla
|
||||||
$adminSettingsService->updateSetting('admin_title', $this->admin_title);
|
app(AdminTemplateService::class)->clearAdminVarsCache();
|
||||||
|
|
||||||
// Procesar favicon si se ha cargado una imagen
|
// Recargamos el formulario
|
||||||
if ($this->upload_image_favicon) {
|
$this->resetForm();
|
||||||
$adminSettingsService->processAndSaveFavicon($this->upload_image_favicon);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->loadSettings(true);
|
|
||||||
|
|
||||||
|
// Notificación de éxito
|
||||||
$this->dispatch(
|
$this->dispatch(
|
||||||
'notification',
|
'notification',
|
||||||
target: $this->targetNotify,
|
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()
|
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
|
<?php
|
||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
namespace Koneko\VuexyAdmin\Livewire\VuexyAdmin;
|
||||||
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
@ -11,9 +11,9 @@ use Symfony\Component\Mime\Email;
|
|||||||
use Koneko\VuexyAdmin\Services\GlobalSettingsService;
|
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,
|
public $change_smtp_settings,
|
||||||
$host,
|
$host,
|
||||||
@ -68,10 +68,7 @@ class MailSmtpSettings extends Component
|
|||||||
|
|
||||||
public function loadSettings()
|
public function loadSettings()
|
||||||
{
|
{
|
||||||
$globalSettingsService = app(GlobalSettingsService::class);
|
$settings = app(GlobalSettingsService::class)->getMailSystemConfig();
|
||||||
|
|
||||||
// Obtener los valores de las configuraciones de la base de datos
|
|
||||||
$settings = $globalSettingsService->getMailSystemConfig();
|
|
||||||
|
|
||||||
$this->change_smtp_settings = false;
|
$this->change_smtp_settings = false;
|
||||||
$this->save_button_disabled = true;
|
$this->save_button_disabled = true;
|
||||||
@ -170,6 +167,6 @@ class MailSmtpSettings extends Component
|
|||||||
|
|
||||||
public function render()
|
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;
|
namespace Koneko\VuexyAdmin\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
class Setting extends Model
|
class Setting extends Model
|
||||||
{
|
{
|
||||||
/**
|
use HasFactory;
|
||||||
* The attributes that are mass assignable.
|
|
||||||
*
|
// ─────────────────────────────────────────────
|
||||||
* @var array<int, string>
|
// Configuración del modelo
|
||||||
*/
|
// ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
protected $table = 'settings';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'key',
|
'key',
|
||||||
'value',
|
'category',
|
||||||
'user_id',
|
'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);
|
return $this->belongsTo(User::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scope para obtener configuraciones de un usuario específico
|
public function updatedBy(): BelongsTo
|
||||||
public function scopeForUser($query, $userId)
|
{
|
||||||
|
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);
|
return $query->where('user_id', $userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configuraciones globales (sin usuario)
|
/**
|
||||||
|
* Configuraciones globales (sin usuario).
|
||||||
|
*/
|
||||||
public function scopeGlobal($query)
|
public function scopeGlobal($query)
|
||||||
{
|
{
|
||||||
return $query->whereNull('user_id');
|
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_DISABLED = 1;
|
||||||
const STATUS_REMOVED = 0;
|
const STATUS_REMOVED = 0;
|
||||||
|
|
||||||
const AVATAR_DISK = 'public';
|
const INITIAL_MAX_LENGTH = 3;
|
||||||
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.
|
* List of names for each status.
|
||||||
@ -148,6 +132,50 @@ class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
|||||||
'email',
|
'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.
|
* Get the full name of the user.
|
||||||
*
|
*
|
||||||
@ -179,45 +207,6 @@ class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
|||||||
$this->notify(new CustomResetPasswordNotification($token));
|
$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
|
* User who created this user
|
||||||
*/
|
*/
|
||||||
@ -233,5 +222,4 @@ class User extends Authenticatable implements MustVerifyEmail, AuditableContract
|
|||||||
{
|
{
|
||||||
return $this->status === self::STATUS_ENABLED;
|
return $this->status === self::STATUS_ENABLED;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class CustomResetPasswordNotification extends Notification
|
|||||||
'email' => $notifiable->getEmailForPasswordReset()
|
'email' => $notifiable->getEmailForPasswordReset()
|
||||||
], false));
|
], 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')));
|
$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);
|
$expireMinutes = Config::get('auth.passwords.' . Config::get('auth.defaults.passwords') . '.expire', 60);
|
||||||
|
|
||||||
@ -90,6 +90,7 @@ class CustomResetPasswordNotification extends Notification
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$smtpConfig = Setting::where('key', 'LIKE', 'mail_%')
|
$smtpConfig = Setting::where('key', 'LIKE', 'mail_%')
|
||||||
|
->withVirtualValue()
|
||||||
->pluck('value', 'key');
|
->pluck('value', 'key');
|
||||||
|
|
||||||
if ($smtpConfig->isEmpty()) {
|
if ($smtpConfig->isEmpty()) {
|
||||||
|
@ -21,10 +21,6 @@ class ConfigServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
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
|
// Cargar configuración del sistema a través del servicio
|
||||||
app(GlobalSettingsService::class)->loadSystemConfig();
|
app(GlobalSettingsService::class)->loadSystemConfig();
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,28 @@
|
|||||||
|
|
||||||
namespace Koneko\VuexyAdmin\Providers;
|
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\Console\Commands\CleanInitialAvatars;
|
||||||
use Koneko\VuexyAdmin\Helpers\VuexyHelper;
|
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\Facades\{URL,Event,Blade};
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Foundation\AliasLoader;
|
use Koneko\VuexyAdmin\Listeners\{ClearUserCache,HandleUserLogin};
|
||||||
use Illuminate\Auth\Events\{Login,Logout};
|
|
||||||
|
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 Livewire\Livewire;
|
||||||
use OwenIt\Auditing\AuditableObserver;
|
use OwenIt\Auditing\AuditableObserver;
|
||||||
use Spatie\Permission\PermissionServiceProvider;
|
use Spatie\Permission\PermissionServiceProvider;
|
||||||
@ -48,14 +56,17 @@ class VuexyAdminServiceProvider extends ServiceProvider
|
|||||||
URL::forceScheme('https');
|
URL::forceScheme('https');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Registrar alias del middleware
|
// Registrar alias del middleware
|
||||||
$this->app['router']->aliasMiddleware('admin', AdminTemplateMiddleware::class);
|
$this->app['router']->aliasMiddleware('admin', AdminTemplateMiddleware::class);
|
||||||
|
|
||||||
|
|
||||||
// Sobrescribir ruta de traducciones para asegurar que se usen las del paquete
|
// Sobrescribir ruta de traducciones para asegurar que se usen las del paquete
|
||||||
$this->app->bind('path.lang', function () {
|
$this->app->bind('path.lang', function () {
|
||||||
return __DIR__ . '/../resources/lang';
|
return __DIR__ . '/../resources/lang';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Register the module's routes
|
// Register the module's routes
|
||||||
$this->loadRoutesFrom(__DIR__.'/../routes/admin.php');
|
$this->loadRoutesFrom(__DIR__.'/../routes/admin.php');
|
||||||
|
|
||||||
@ -63,6 +74,7 @@ class VuexyAdminServiceProvider extends ServiceProvider
|
|||||||
// Cargar vistas del paquete
|
// Cargar vistas del paquete
|
||||||
$this->loadViewsFrom(__DIR__.'/../resources/views', 'vuexy-admin');
|
$this->loadViewsFrom(__DIR__.'/../resources/views', 'vuexy-admin');
|
||||||
|
|
||||||
|
|
||||||
// Registrar Componentes Blade
|
// Registrar Componentes Blade
|
||||||
Blade::componentNamespace('VuexyAdmin\\View\\Components', 'vuexy-admin');
|
Blade::componentNamespace('VuexyAdmin\\View\\Components', 'vuexy-admin');
|
||||||
|
|
||||||
@ -100,32 +112,60 @@ class VuexyAdminServiceProvider extends ServiceProvider
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Registrar Livewire Components
|
// Registrar Livewire Components
|
||||||
$components = [
|
$components = [
|
||||||
'user-index' => UserIndex::class,
|
// Usuarios
|
||||||
'user-show' => UserShow::class,
|
'vuexy-admin::users-index' => UsersIndex::class,
|
||||||
'user-form' => UserForm::class,
|
'vuexy-admin::users-count' => UsersCount::class,
|
||||||
'user-offcanvas-form' => UserOffCanvasForm::class,
|
'vuexy-admin::user-form' => UserForm::class,
|
||||||
'role-index' => RoleIndex::class,
|
'vuexy-admin::user-offcanvas-form' => UserOffCanvasForm::class,
|
||||||
'permission-index' => PermissionIndex::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,
|
// Roles y Permisos
|
||||||
'application-settings' => ApplicationSettings::class,
|
'vuexy-admin::roles-index' => RolesIndex::class,
|
||||||
'interface-settings' => InterfaceSettings::class,
|
'vuexy-admin::role-cards' => RoleCards::class,
|
||||||
'mail-smtp-settings' => MailSmtpSettings::class,
|
'vuexy-admin::permissions-index' => PermissionsIndex::class,
|
||||||
'mail-sender-response-settings' => MailSenderResponseSettings::class,
|
'vuexy-admin::permission-offcanvas-form' => PermissionOffCanvasForm::class,
|
||||||
'cache-stats' => CacheStats::class,
|
|
||||||
'session-stats' => SessionStats::class,
|
// Identidad de aplicación
|
||||||
'redis-stats' => RedisStats::class,
|
'vuexy-admin::app-description-settings' => AppDescriptionSettings::class,
|
||||||
'memcached-stats' => MemcachedStats::class,
|
'vuexy-admin::app-favicon-settings' => AppFaviconSettings::class,
|
||||||
'cache-functions' => CacheFunctions::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) {
|
foreach ($components as $alias => $component) {
|
||||||
Livewire::component($alias, $component);
|
Livewire::component($alias, $component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Registrar auditoría en usuarios
|
// Registrar auditoría en usuarios
|
||||||
User::observe(AuditableObserver::class);
|
User::observe(AuditableObserver::class);
|
||||||
}
|
}
|
||||||
|
@ -27,19 +27,24 @@ abstract class BootstrapTableQueryBuilder
|
|||||||
foreach ($this->config['joins'] as $join) {
|
foreach ($this->config['joins'] as $join) {
|
||||||
$type = $join['type'] ?? 'join';
|
$type = $join['type'] ?? 'join';
|
||||||
|
|
||||||
$this->query->{$type}($join['table'], function($joinObj) use ($join) {
|
// Soporte para alias
|
||||||
$joinObj->on($join['first'], '=', $join['second']);
|
$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'])) {
|
if (!empty($join['and'])) {
|
||||||
foreach ((array) $join['and'] as $andCondition) {
|
foreach ((array) $join['and'] as $andCondition) {
|
||||||
// 'sat_codigo_postal.c_estado = sat_localidad.c_estado'
|
|
||||||
$parts = explode('=', $andCondition);
|
$parts = explode('=', $andCondition);
|
||||||
|
|
||||||
if (count($parts) === 2) {
|
if (count($parts) === 2) {
|
||||||
$left = trim($parts[0]);
|
$left = trim($parts[0]);
|
||||||
$right = trim($parts[1]);
|
$right = trim($parts[1]);
|
||||||
|
|
||||||
$joinObj->whereRaw("$left = $right");
|
$joinObj->whereRaw("$left = $right");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,29 @@ use Illuminate\Support\Facades\Storage;
|
|||||||
use Intervention\Image\ImageManager;
|
use Intervention\Image\ImageManager;
|
||||||
use Koneko\VuexyAdmin\Models\Setting;
|
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
|
class AdminSettingsService
|
||||||
{
|
{
|
||||||
|
/** @var string Driver de procesamiento de imágenes */
|
||||||
private $driver;
|
private $driver;
|
||||||
|
|
||||||
|
/** @var string Disco de almacenamiento para imágenes */
|
||||||
private $imageDisk = 'public';
|
private $imageDisk = 'public';
|
||||||
|
|
||||||
|
/** @var string Ruta base para favicons */
|
||||||
private $favicon_basePath = 'favicon/';
|
private $favicon_basePath = 'favicon/';
|
||||||
|
|
||||||
|
/** @var string Ruta base para logos */
|
||||||
private $image_logo_basePath = 'images/logo/';
|
private $image_logo_basePath = 'images/logo/';
|
||||||
|
|
||||||
|
/** @var array<string,array<int>> Tamaños predefinidos para favicons */
|
||||||
private $faviconsSizes = [
|
private $faviconsSizes = [
|
||||||
'180x180' => [180, 180],
|
'180x180' => [180, 180],
|
||||||
'192x192' => [192, 192],
|
'192x192' => [192, 192],
|
||||||
@ -22,28 +38,40 @@ class AdminSettingsService
|
|||||||
'16x16' => [16, 16],
|
'16x16' => [16, 16],
|
||||||
];
|
];
|
||||||
|
|
||||||
private $imageLogoMaxPixels1 = 22500; // Primera versión (px^2)
|
/** @var int Área máxima en píxeles para la primera versión del logo */
|
||||||
private $imageLogoMaxPixels2 = 75625; // Segunda versión (px^2)
|
private $imageLogoMaxPixels1 = 22500;
|
||||||
private $imageLogoMaxPixels3 = 262144; // Tercera versión (px^2)
|
|
||||||
private $imageLogoMaxPixels4 = 230400; // Tercera versión (px^2) en Base64
|
|
||||||
|
|
||||||
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()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->driver = config('image.driver', 'gd');
|
$this->driver = config('image.driver', 'gd');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function updateSetting(string $key, string $value): bool
|
/**
|
||||||
{
|
* Procesa y guarda un nuevo favicon
|
||||||
$setting = Setting::updateOrCreate(
|
*
|
||||||
['key' => $key],
|
* Genera múltiples versiones del favicon en diferentes tamaños predefinidos,
|
||||||
['value' => trim($value)]
|
* elimina las versiones anteriores y actualiza la configuración.
|
||||||
);
|
*
|
||||||
|
* @param \Illuminate\Http\UploadedFile $image Archivo de imagen subido
|
||||||
return $setting->save();
|
* @return void
|
||||||
}
|
*/
|
||||||
|
|
||||||
public function processAndSaveFavicon($image): void
|
public function processAndSaveFavicon($image): void
|
||||||
{
|
{
|
||||||
Storage::makeDirectory($this->imageDisk . '/' . $this->favicon_basePath);
|
Storage::makeDirectory($this->imageDisk . '/' . $this->favicon_basePath);
|
||||||
@ -66,13 +94,20 @@ class AdminSettingsService
|
|||||||
Storage::disk($this->imageDisk)->put($resizedPath, $image->toPng(indexed: true));
|
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
|
protected function deleteOldFavicons(): void
|
||||||
{
|
{
|
||||||
// Obtener el favicon actual desde la base de datos
|
// 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) {
|
if ($currentFavicon) {
|
||||||
$filePaths = [
|
$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
|
public function processAndSaveImageLogo($image, string $type = ''): void
|
||||||
{
|
{
|
||||||
// Crear directorio si no existe
|
// Crear directorio si no existe
|
||||||
@ -112,6 +157,15 @@ class AdminSettingsService
|
|||||||
$this->generateAndSaveImageAsBase64($image, $type, $this->imageLogoMaxPixels4); // Versión 3
|
$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
|
private function generateAndSaveImage($image, string $type, int $maxPixels, string $suffix = ''): void
|
||||||
{
|
{
|
||||||
$imageClone = clone $image;
|
$imageClone = clone $image;
|
||||||
@ -120,6 +174,7 @@ class AdminSettingsService
|
|||||||
$this->resizeImageToMaxPixels($imageClone, $maxPixels);
|
$this->resizeImageToMaxPixels($imageClone, $maxPixels);
|
||||||
|
|
||||||
$imageName = 'admin_image_logo' . ($suffix ? '_' . $suffix : '') . ($type == 'dark' ? '_dark' : '');
|
$imageName = 'admin_image_logo' . ($suffix ? '_' . $suffix : '') . ($type == 'dark' ? '_dark' : '');
|
||||||
|
$keyValue = 'admin.image.logo' . ($suffix ? '_' . $suffix : '') . ($type == 'dark' ? '_dark' : '');
|
||||||
|
|
||||||
// Generar nombre y ruta
|
// Generar nombre y ruta
|
||||||
$imageNameUid = uniqid($imageName . '_', ".png");
|
$imageNameUid = uniqid($imageName . '_', ".png");
|
||||||
@ -129,9 +184,17 @@ class AdminSettingsService
|
|||||||
Storage::disk($this->imageDisk)->put($resizedPath, $imageClone->toPng(indexed: true));
|
Storage::disk($this->imageDisk)->put($resizedPath, $imageClone->toPng(indexed: true));
|
||||||
|
|
||||||
// Actualizar configuración
|
// 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)
|
private function resizeImageToMaxPixels($image, int $maxPixels)
|
||||||
{
|
{
|
||||||
// Obtener dimensiones originales de la imagen
|
// Obtener dimensiones originales de la imagen
|
||||||
@ -163,7 +226,14 @@ class AdminSettingsService
|
|||||||
return $image;
|
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
|
private function generateAndSaveImageAsBase64($image, string $type, int $maxPixels): void
|
||||||
{
|
{
|
||||||
$imageClone = clone $image;
|
$imageClone = clone $image;
|
||||||
@ -175,12 +245,16 @@ class AdminSettingsService
|
|||||||
$base64Image = (string) $imageClone->toJpg(40)->toDataUri();
|
$base64Image = (string) $imageClone->toJpg(40)->toDataUri();
|
||||||
|
|
||||||
// Guardar como configuración
|
// Guardar como configuración
|
||||||
$this->updateSetting(
|
$SettingsService = app(SettingsService::class);
|
||||||
"admin_image_logo_base64" . ($type === 'dark' ? '_dark' : ''),
|
$SettingsService->set("admin.image.logo_base64" . ($type === 'dark' ? '_dark' : ''), $base64Image, null, 'vuexy-admin');
|
||||||
$base64Image // Ya incluye "data:image/png;base64,"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
protected function deleteOldImageWebapp(string $type = ''): void
|
||||||
{
|
{
|
||||||
// Determinar prefijo según el tipo (normal o dark)
|
// Determinar prefijo según el tipo (normal o dark)
|
||||||
@ -188,9 +262,9 @@ class AdminSettingsService
|
|||||||
|
|
||||||
// Claves relacionadas con las imágenes que queremos limpiar
|
// Claves relacionadas con las imágenes que queremos limpiar
|
||||||
$imageKeys = [
|
$imageKeys = [
|
||||||
"admin_image_logo{$suffix}",
|
"admin.image_logo{$suffix}",
|
||||||
"admin_image_logo_small{$suffix}",
|
"admin.image_logo_small{$suffix}",
|
||||||
"admin_image_logo_medium{$suffix}",
|
"admin.image_logo_medium{$suffix}",
|
||||||
];
|
];
|
||||||
|
|
||||||
// Recuperar las imágenes actuales en una sola consulta
|
// Recuperar las imágenes actuales en una sola consulta
|
||||||
|
@ -7,48 +7,57 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
use Koneko\VuexyAdmin\Models\Setting;
|
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
|
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
|
/**
|
||||||
{
|
* Obtiene las variables de configuración del admin.
|
||||||
$setting = Setting::updateOrCreate(
|
*
|
||||||
['key' => $key],
|
* @param string $setting Clave específica de configuración a obtener
|
||||||
['value' => trim($value)]
|
* @return array Configuraciones del admin o valor específico si se proporciona $setting
|
||||||
);
|
*/
|
||||||
|
public function getAdminVars(string $setting = ''): array
|
||||||
return $setting->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAdminVars($adminSetting = false): array
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// Verificar si el sistema está inicializado (la tabla `migrations` existe)
|
// Verificar si el sistema está inicializado (la tabla `migrations` existe)
|
||||||
if (!Schema::hasTable('migrations')) {
|
if (!Schema::hasTable('migrations')) {
|
||||||
return $this->getDefaultAdminVars($adminSetting);
|
return $this->getDefaultAdminVars($setting);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cargar desde el caché o la base de datos si está disponible
|
// Cargar desde el caché o la base de datos si está disponible
|
||||||
return Cache::remember('admin_settings', $this->cacheTTL, function () use ($adminSetting) {
|
$adminVars = Cache::remember('admin_settings', $this->cacheTTL, function () {
|
||||||
$settings = Setting::global()
|
$settings = Setting::withVirtualValue()
|
||||||
->where('key', 'LIKE', 'admin_%')
|
->where('key', 'LIKE', 'admin.%')
|
||||||
->pluck('value', 'key')
|
->pluck('value', 'key')
|
||||||
->toArray();
|
->toArray();
|
||||||
|
|
||||||
$adminSettings = $this->buildAdminVarsArray($settings);
|
return $this->buildAdminVarsArray($settings);
|
||||||
|
|
||||||
return $adminSetting
|
|
||||||
? $adminSettings[$adminSetting]
|
|
||||||
: $adminSettings;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return $setting ? ($adminVars[$setting] ?? []) : $adminVars;
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// En caso de error, devolver valores predeterminados
|
// 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 = [
|
$defaultSettings = [
|
||||||
'title' => config('koneko.appTitle', 'Default Title'),
|
'title' => config('koneko.appTitle', 'Default Title'),
|
||||||
@ -59,27 +68,41 @@ class AdminTemplateService
|
|||||||
'image_logo' => $this->getImageLogoPaths([]),
|
'image_logo' => $this->getImageLogoPaths([]),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $adminSetting
|
return $setting
|
||||||
? $defaultSettings[$adminSetting] ?? null
|
? $defaultSettings[$setting] ?? null
|
||||||
: $defaultSettings;
|
: $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
|
private function buildAdminVarsArray(array $settings): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'title' => $settings['admin_title'] ?? config('koneko.appTitle'),
|
'title' => $settings['admin.title'] ?? config('koneko.appTitle'),
|
||||||
'author' => config('koneko.author'),
|
'author' => config('koneko.author'),
|
||||||
'description' => config('koneko.description'),
|
'description' => $settings['admin.description'] ?? config('koneko.description'),
|
||||||
'favicon' => $this->getFaviconPaths($settings),
|
'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),
|
'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()
|
public function getVuexyCustomizerVars()
|
||||||
{
|
{
|
||||||
// Obtener valores de la base de datos
|
// Obtener valores de la base de datos
|
||||||
$settings = Setting::global()
|
$settings = Setting::withVirtualValue()
|
||||||
->where('key', 'LIKE', 'vuexy_%')
|
->where('key', 'LIKE', 'vuexy_%')
|
||||||
->pluck('value', 'key')
|
->pluck('value', 'key')
|
||||||
->toArray();
|
->toArray();
|
||||||
@ -96,7 +119,7 @@ class AdminTemplateService
|
|||||||
$value = $settings[$vuexyKey] ?? $defaultValue;
|
$value = $settings[$vuexyKey] ?? $defaultValue;
|
||||||
|
|
||||||
// Forzar booleanos para claves específicas
|
// 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);
|
$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
|
private function getFaviconPaths(array $settings): array
|
||||||
{
|
{
|
||||||
$defaultFavicon = config('koneko.appFavicon');
|
$defaultFavicon = config('koneko.appFavicon');
|
||||||
$namespace = $settings['admin_favicon_ns'] ?? null;
|
$namespace = $settings['admin.favicon_ns'] ?? null;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'namespace' => $namespace,
|
'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
|
private function getImageLogoPaths(array $settings): array
|
||||||
{
|
{
|
||||||
$defaultLogo = config('koneko.appLogo');
|
$defaultLogo = config('koneko.appLogo');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'small' => $this->getImagePath($settings, 'admin_image_logo_small', $defaultLogo),
|
'small' => $this->getImagePath($settings, 'admin.image.logo_small', $defaultLogo),
|
||||||
'medium' => $this->getImagePath($settings, 'admin_image_logo_medium', $defaultLogo),
|
'medium' => $this->getImagePath($settings, 'admin.image.logo_medium', $defaultLogo),
|
||||||
'large' => $this->getImagePath($settings, 'admin_image_logo', $defaultLogo),
|
'large' => $this->getImagePath($settings, 'admin.image.logo', $defaultLogo),
|
||||||
'small_dark' => $this->getImagePath($settings, 'admin_image_logo_small_dark', $defaultLogo),
|
'small_dark' => $this->getImagePath($settings, 'admin.image.logo_small_dark', $defaultLogo),
|
||||||
'medium_dark' => $this->getImagePath($settings, 'admin_image_logo_medium_dark', $defaultLogo),
|
'medium_dark' => $this->getImagePath($settings, 'admin.image.logo_medium_dark', $defaultLogo),
|
||||||
'large_dark' => $this->getImagePath($settings, 'admin_image_logo_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
|
private function getImagePath(array $settings, string $key, string $default): string
|
||||||
{
|
{
|
||||||
return $settings[$key] ?? $default;
|
return $settings[$key] ?? $default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia el caché de las variables del admin.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
public static function clearAdminVarsCache()
|
public static function clearAdminVarsCache()
|
||||||
{
|
{
|
||||||
Cache::forget("admin_settings");
|
Cache::forget("admin_settings");
|
||||||
|
@ -15,7 +15,7 @@ class AvatarInitialsService
|
|||||||
protected const INITIAL_MAX_LENGTH = 3;
|
protected const INITIAL_MAX_LENGTH = 3;
|
||||||
protected const AVATAR_BACKGROUND = '#EBF4FF';
|
protected const AVATAR_BACKGROUND = '#EBF4FF';
|
||||||
protected const AVATAR_COLORS = [
|
protected const AVATAR_COLORS = [
|
||||||
'#7367f0',
|
'#3b82f6',
|
||||||
'#808390',
|
'#808390',
|
||||||
'#28c76f',
|
'#28c76f',
|
||||||
'#ff4c51',
|
'#ff4c51',
|
||||||
|
@ -6,8 +6,20 @@ use Illuminate\Support\Facades\Config;
|
|||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Redis;
|
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
|
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
|
public function getConfig(): array
|
||||||
{
|
{
|
||||||
return [
|
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
|
private function getCacheConfig(): array
|
||||||
{
|
{
|
||||||
$cacheConfig = Config::get('cache');
|
$cacheConfig = Config::get('cache');
|
||||||
@ -59,6 +75,11 @@ class CacheConfigService
|
|||||||
return $cacheConfig;
|
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
|
private function getSessionConfig(): array
|
||||||
{
|
{
|
||||||
$sessionConfig = Config::get('session');
|
$sessionConfig = Config::get('session');
|
||||||
@ -97,6 +118,11 @@ class CacheConfigService
|
|||||||
return $sessionConfig;
|
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
|
private function getDatabaseConfig(): array
|
||||||
{
|
{
|
||||||
$databaseConfig = Config::get('database');
|
$databaseConfig = Config::get('database');
|
||||||
@ -109,7 +135,14 @@ class CacheConfigService
|
|||||||
return $databaseConfig;
|
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
|
private function getDriverVersion(): array
|
||||||
{
|
{
|
||||||
$drivers = [];
|
$drivers = [];
|
||||||
@ -163,6 +196,11 @@ class CacheConfigService
|
|||||||
return $drivers;
|
return $drivers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene la versión del servidor MySQL.
|
||||||
|
*
|
||||||
|
* @return string Versión del servidor MySQL o mensaje de error
|
||||||
|
*/
|
||||||
private function getMySqlVersion(): string
|
private function getMySqlVersion(): string
|
||||||
{
|
{
|
||||||
try {
|
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
|
private function getPgSqlVersion(): string
|
||||||
{
|
{
|
||||||
try {
|
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
|
private function getSqlSrvVersion(): string
|
||||||
{
|
{
|
||||||
try {
|
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
|
private function getMemcachedVersion(): string
|
||||||
{
|
{
|
||||||
try {
|
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
|
private function getRedisVersion(): string
|
||||||
{
|
{
|
||||||
try {
|
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
|
protected function isDriverInUse(string $driver): bool
|
||||||
{
|
{
|
||||||
return in_array($driver, [
|
return in_array($driver, [
|
||||||
|
@ -7,19 +7,38 @@ use Illuminate\Support\Facades\DB;
|
|||||||
use Illuminate\Support\Facades\Redis;
|
use Illuminate\Support\Facades\Redis;
|
||||||
use Illuminate\Support\Facades\File;
|
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
|
class CacheManagerService
|
||||||
{
|
{
|
||||||
|
/** @var string Driver de caché actualmente seleccionado */
|
||||||
private string $driver;
|
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');
|
$this->driver = $driver ?? config('cache.default');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtiene estadísticas de caché para el driver especificado.
|
* 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;
|
$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;
|
$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()
|
public function getRedisStats()
|
||||||
{
|
{
|
||||||
try {
|
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()
|
public function getMemcachedStats()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
@ -176,9 +211,10 @@ class CacheManagerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtiene estadísticas para caché en base de datos.
|
* 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
|
private function _getDatabaseStats(): array
|
||||||
{
|
{
|
||||||
@ -196,6 +232,8 @@ class CacheManagerService
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtiene estadísticas para caché en archivos.
|
* 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
|
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()
|
private function _getRedisStats()
|
||||||
{
|
{
|
||||||
try {
|
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
|
public function _getMemcachedStats(): array
|
||||||
{
|
{
|
||||||
try {
|
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
|
private function getRedisDatabases(): array
|
||||||
{
|
{
|
||||||
// Verificar si Redis está en uso
|
// Verificar si Redis está en uso
|
||||||
@ -300,7 +356,11 @@ class CacheManagerService
|
|||||||
return $result;
|
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
|
private function clearDatabaseCache(): bool
|
||||||
{
|
{
|
||||||
$count = DB::table(config('cache.stores.database.table'))->count();
|
$count = DB::table(config('cache.stores.database.table'))->count();
|
||||||
@ -313,6 +373,11 @@ class CacheManagerService
|
|||||||
return false;
|
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
|
private function clearFilecache(): bool
|
||||||
{
|
{
|
||||||
$cachePath = config('cache.stores.file.path');
|
$cachePath = config('cache.stores.file.path');
|
||||||
@ -326,6 +391,11 @@ class CacheManagerService
|
|||||||
return false;
|
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
|
private function clearRedisCache(): bool
|
||||||
{
|
{
|
||||||
$prefix = config('cache.prefix', '');
|
$prefix = config('cache.prefix', '');
|
||||||
@ -343,6 +413,11 @@ class CacheManagerService
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limpia la caché almacenada en Memcached.
|
||||||
|
*
|
||||||
|
* @return bool True si se limpió la caché, False en caso contrario
|
||||||
|
*/
|
||||||
private function clearMemcachedCache(): bool
|
private function clearMemcachedCache(): bool
|
||||||
{
|
{
|
||||||
// Obtener el cliente Memcached directamente
|
// Obtener el cliente Memcached directamente
|
||||||
@ -359,9 +434,11 @@ class CacheManagerService
|
|||||||
return false;
|
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
|
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)
|
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
|
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 Illuminate\Support\Facades\Schema;
|
||||||
use Koneko\VuexyAdmin\Models\Setting;
|
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
|
class GlobalSettingsService
|
||||||
{
|
{
|
||||||
/**
|
/** @var int Tiempo de vida del caché en minutos (60 * 24 * 30 = 30 días) */
|
||||||
* Tiempo de vida del caché en minutos (30 días).
|
|
||||||
*/
|
|
||||||
private $cacheTTL = 60 * 24 * 30;
|
private $cacheTTL = 60 * 24 * 30;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actualiza o crea una configuración.
|
* Carga la configuración del sistema desde la base de datos o caché.
|
||||||
*/
|
*
|
||||||
public function updateSetting(string $key, string $value): bool
|
* Gestiona la carga de configuraciones para servicios externos y Vuexy.
|
||||||
{
|
* Si la base de datos no está inicializada, utiliza valores predeterminados.
|
||||||
$setting = Setting::updateOrCreate(
|
*
|
||||||
['key' => $key],
|
* @return void
|
||||||
['value' => trim($value)]
|
|
||||||
);
|
|
||||||
|
|
||||||
return $setting->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Carga y sobrescribe las configuraciones del sistema.
|
|
||||||
*/
|
*/
|
||||||
public function loadSystemConfig(): void
|
public function loadSystemConfig(): void
|
||||||
{
|
{
|
||||||
@ -41,7 +39,7 @@ class GlobalSettingsService
|
|||||||
} else {
|
} else {
|
||||||
// Cargar configuración desde la caché o base de datos
|
// Cargar configuración desde la caché o base de datos
|
||||||
$config = Cache::remember('global_system_config', $this->cacheTTL, function () {
|
$config = Cache::remember('global_system_config', $this->cacheTTL, function () {
|
||||||
$settings = Setting::global()
|
$settings = Setting::withVirtualValue()
|
||||||
->where('key', 'LIKE', 'config.%')
|
->where('key', 'LIKE', 'config.%')
|
||||||
->pluck('value', 'key')
|
->pluck('value', 'key')
|
||||||
->toArray();
|
->toArray();
|
||||||
@ -58,6 +56,7 @@ class GlobalSettingsService
|
|||||||
Config::set('services.facebook', $config['servicesFacebook']);
|
Config::set('services.facebook', $config['servicesFacebook']);
|
||||||
Config::set('services.google', $config['servicesGoogle']);
|
Config::set('services.google', $config['servicesGoogle']);
|
||||||
Config::set('vuexy', $config['vuexy']);
|
Config::set('vuexy', $config['vuexy']);
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
// Manejo silencioso de errores para evitar interrupciones
|
// Manejo silencioso de errores para evitar interrupciones
|
||||||
Config::set('services.facebook', config('services.facebook', []));
|
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
|
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
|
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
|
protected function buildServiceConfig(array $settings, string $blockPrefix, string $defaultConfigKey): array
|
||||||
{
|
{
|
||||||
if (!$this->hasBlockConfig($settings, $blockPrefix)) {
|
if (!$this->hasBlockConfig($settings, $blockPrefix)) {
|
||||||
return [];
|
return config($defaultConfigKey)?? [];
|
||||||
return config($defaultConfigKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -112,7 +121,13 @@ 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
|
protected function buildVuexyConfig(array $settings): array
|
||||||
{
|
{
|
||||||
@ -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
|
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
|
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
|
public static function clearVuexyConfig(): void
|
||||||
{
|
{
|
||||||
Setting::where('key', 'LIKE', 'config.vuexy.%')->delete();
|
Setting::where('key', 'LIKE', 'config.vuexy.%')->delete();
|
||||||
|
|
||||||
Cache::forget('global_system_config');
|
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
|
public function getMailSystemConfig(): array
|
||||||
{
|
{
|
||||||
return Cache::remember('mail_system_config', $this->cacheTTL, function () {
|
return Cache::remember('mail_system_config', $this->cacheTTL, function () {
|
||||||
$settings = Setting::global()
|
$settings = Setting::withVirtualValue()
|
||||||
->where('key', 'LIKE', 'mail.%')
|
->where('key', 'LIKE', 'mail.%')
|
||||||
->pluck('value', 'key')
|
->pluck('value', 'key')
|
||||||
->toArray();
|
->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
|
public static function clearMailSystemConfigCache(): void
|
||||||
{
|
{
|
||||||
|
@ -10,12 +10,12 @@ class SessionManagerService
|
|||||||
{
|
{
|
||||||
private string $driver;
|
private string $driver;
|
||||||
|
|
||||||
public function __construct(string $driver = null)
|
public function __construct(mixed $driver = null)
|
||||||
{
|
{
|
||||||
$this->driver = $driver ?? config('session.driver');
|
$this->driver = $driver ?? config('session.driver');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getSessionStats(string $driver = null): array
|
public function getSessionStats(mixed $driver = null): array
|
||||||
{
|
{
|
||||||
$driver = $driver ?? $this->driver;
|
$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;
|
$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;
|
namespace Koneko\VuexyAdmin\Services;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\{Auth,Cache,Route,Gate};
|
||||||
use Illuminate\Support\Facades\Cache;
|
|
||||||
use Illuminate\Support\Facades\Route;
|
|
||||||
use Illuminate\Support\Facades\Gate;
|
|
||||||
use Koneko\VuexyAdmin\Models\Setting;
|
use Koneko\VuexyAdmin\Models\Setting;
|
||||||
|
|
||||||
class VuexyAdminService
|
class VuexyAdminService
|
||||||
@ -26,12 +23,8 @@ class VuexyAdminService
|
|||||||
{
|
{
|
||||||
$this->user = Auth::user();
|
$this->user = Auth::user();
|
||||||
$this->vuexySearch = Auth::user() !== null;
|
$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()
|
public function getMenu()
|
||||||
{
|
{
|
||||||
// Obtener el menú desde la caché
|
// Obtener el menú desde la caché
|
||||||
@ -45,9 +38,6 @@ class VuexyAdminService
|
|||||||
return $this->markActive($menu, $currentRoute);
|
return $this->markActive($menu, $currentRoute);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Menú para usuarios no autenticados.dump
|
|
||||||
*/
|
|
||||||
private function getGuestMenu()
|
private function getGuestMenu()
|
||||||
{
|
{
|
||||||
return Cache::remember('vuexy_menu_guest', now()->addDays(7), function () {
|
return Cache::remember('vuexy_menu_guest', now()->addDays(7), function () {
|
||||||
@ -55,12 +45,9 @@ class VuexyAdminService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Menú para usuarios autenticados.
|
|
||||||
*/
|
|
||||||
private function getUserMenu()
|
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 Cache::remember("vuexy_menu_user_{$this->user->id}", now()->addHours(24), function () {
|
||||||
return $this->getMenuArray();
|
return $this->getMenuArray();
|
||||||
@ -89,9 +76,6 @@ class VuexyAdminService
|
|||||||
return $menu;
|
return $menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalida el cache del menú de un usuario.
|
|
||||||
*/
|
|
||||||
public static function clearUserMenuCache()
|
public static function clearUserMenuCache()
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
@ -100,9 +84,6 @@ class VuexyAdminService
|
|||||||
Cache::forget("vuexy_menu_user_{$user->id}");
|
Cache::forget("vuexy_menu_user_{$user->id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalida el cache del menú de invitados.
|
|
||||||
*/
|
|
||||||
public static function clearGuestMenuCache()
|
public static function clearGuestMenuCache()
|
||||||
{
|
{
|
||||||
Cache::forget('vuexy_menu_guest');
|
Cache::forget('vuexy_menu_guest');
|
||||||
@ -224,7 +205,7 @@ class VuexyAdminService
|
|||||||
return $quickLinksData;
|
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) {
|
foreach ($menu as $title => $item) {
|
||||||
// Verificar si el elemento está en la lista de quicklinksRouteNames
|
// 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
|
private function isCurrentPageInList(array $quickLinks, string $currentRoute): bool
|
||||||
{
|
{
|
||||||
foreach ($quickLinks['rows'] as $row) {
|
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'>
|
<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'>
|
<span class='position-relative'>
|
||||||
<i class='ti ti-bell ti-md'></i>
|
<i class='ti ti-bell ti-md'></i>
|
||||||
<span class='badge rounded-pill bg-danger badge-dot badge-notifications border'></span>
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</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>";
|
</li>";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,9 +14,10 @@
|
|||||||
"owen-it/laravel-auditing": "^13.6",
|
"owen-it/laravel-auditing": "^13.6",
|
||||||
"spatie/laravel-permission": "^6.10"
|
"spatie/laravel-permission": "^6.10"
|
||||||
},
|
},
|
||||||
|
"prefer-stable": true,
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Koneko\\VuexyAdmin\\": ""
|
"Koneko\\VuexyAdmin\\": "./"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
@ -35,6 +36,5 @@
|
|||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/koneko-mx/laravel-vuexy-admin",
|
"source": "https://github.com/koneko-mx/laravel-vuexy-admin",
|
||||||
"issues": "https://github.com/koneko-mx/laravel-vuexy-admin/issues"
|
"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',
|
'route' => 'admin.core.user-profile.index',
|
||||||
'icon' => 'menu-icon tf-icons ti ti-user-cog',
|
'icon' => 'menu-icon tf-icons ti ti-user-cog',
|
||||||
],
|
],
|
||||||
@ -377,14 +377,14 @@ return [
|
|||||||
'can' => 'admin.inventory.product-catalogs.view',
|
'can' => 'admin.inventory.product-catalogs.view',
|
||||||
],
|
],
|
||||||
'Productos y servicios' => [
|
'Productos y servicios' => [
|
||||||
'route' => 'admin.inventory.products.index',
|
'route' => 'admin.products.products.index',
|
||||||
'icon' => 'menu-icon tf-icons ti ti-packages',
|
'icon' => 'menu-icon tf-icons ti ti-packages',
|
||||||
'can' => 'admin.inventory.products.view',
|
'can' => 'admin.products.products.view',
|
||||||
],
|
],
|
||||||
'Agregar producto o servicio' => [
|
'Agregar producto o servicio' => [
|
||||||
'route' => 'admin.inventory.products.create',
|
'route' => 'admin.products.products.create',
|
||||||
'icon' => 'menu-icon tf-icons ti ti-package',
|
'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',
|
'can' => 'admin.inventory.suppliers.view',
|
||||||
],
|
],
|
||||||
'Órdenes de Compra' => [
|
'Órdenes de Compra' => [
|
||||||
'route' => 'admin.inventory.orders.index',
|
'route' => 'admin.purchase-orders.orders.index',
|
||||||
'can' => 'admin.inventory.orders.view',
|
'can' => 'admin.purchase-orders.orders.view',
|
||||||
],
|
],
|
||||||
'Recepción de Productos' => [
|
'Recepción de Productos' => [
|
||||||
'route' => 'admin.inventory.reception.index',
|
'route' => 'admin.purchase-orders.reception.index',
|
||||||
'can' => 'admin.inventory.reception.view',
|
'can' => 'admin.purchase-orders.reception.view',
|
||||||
],
|
],
|
||||||
'Gestión de Insumos' => [
|
'Gestión de Insumos' => [
|
||||||
'route' => 'admin.inventory.materials.index',
|
'route' => 'admin.purchase-orders.materials.index',
|
||||||
'can' => 'admin.inventory.materials.view',
|
'can' => 'admin.purchase-orders.materials.view',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -654,20 +654,20 @@ return [
|
|||||||
'icon' => 'menu-icon tf-icons ti ti-truck',
|
'icon' => 'menu-icon tf-icons ti ti-truck',
|
||||||
'submenu' => [
|
'submenu' => [
|
||||||
'Órdenes de Envío' => [
|
'Órdenes de Envío' => [
|
||||||
'route' => 'admin.inventory.shipping-orders.index',
|
'route' => 'admin.shipping.orders.index',
|
||||||
'can' => 'admin.inventory.shipping-orders.view',
|
'can' => 'admin.shipping.orders.view',
|
||||||
],
|
],
|
||||||
'Seguimiento de Envíos' => [
|
'Seguimiento de Envíos' => [
|
||||||
'route' => 'admin.inventory.shipping-tracking.index',
|
'route' => 'admin.shipping.tracking.index',
|
||||||
'can' => 'admin.inventory.shipping-tracking.view',
|
'can' => 'admin.shipping.tracking.view',
|
||||||
],
|
],
|
||||||
'Transportistas' => [
|
'Transportistas' => [
|
||||||
'route' => 'admin.inventory.shipping-carriers.index',
|
'route' => 'admin.shipping.carriers.index',
|
||||||
'can' => 'admin.inventory.shipping-carriers.view',
|
'can' => 'admin.shipping.carriers.view',
|
||||||
],
|
],
|
||||||
'Tarifas y Métodos de Envío' => [
|
'Tarifas y Métodos de Envío' => [
|
||||||
'route' => 'admin.inventory.shipping-rates.index',
|
'route' => 'admin.shipping.rates.index',
|
||||||
'can' => 'admin.inventory.shipping-rates.view',
|
'can' => 'admin.shipping.rates.view',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@ -679,16 +679,16 @@ return [
|
|||||||
'can' => 'admin.inventory.asset.view',
|
'can' => 'admin.inventory.asset.view',
|
||||||
],
|
],
|
||||||
'Mantenimiento Preventivo' => [
|
'Mantenimiento Preventivo' => [
|
||||||
'route' => 'admin.inventory.asset-maintenance.index',
|
'route' => 'admin.assets.maintenance.index',
|
||||||
'can' => 'admin.inventory.asset-maintenance.view',
|
'can' => 'admin.assets.maintenance.view',
|
||||||
],
|
],
|
||||||
'Control de Vida Útil' => [
|
'Control de Vida Útil' => [
|
||||||
'route' => 'admin.inventory.asset-lifecycle.index',
|
'route' => 'admin.assets.lifecycle.index',
|
||||||
'can' => 'admin.inventory.asset-lifecycle.view',
|
'can' => 'admin.assets.lifecycle.view',
|
||||||
],
|
],
|
||||||
'Asignación de Activos' => [
|
'Asignación de Activos' => [
|
||||||
'route' => 'admin.inventory.asset-assignments.index',
|
'route' => 'admin.assets.assignments.index',
|
||||||
'can' => 'admin.inventory.asset-assignments.view',
|
'can' => 'admin.assets.assignments.view',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
@ -59,8 +59,8 @@
|
|||||||
"admin.attendance.absences.view",
|
"admin.attendance.absences.view",
|
||||||
"admin.inventory.product-categories.view",
|
"admin.inventory.product-categories.view",
|
||||||
"admin.inventory.product-catalogs.view",
|
"admin.inventory.product-catalogs.view",
|
||||||
"admin.inventory.products.view",
|
"admin.products.products.view",
|
||||||
"admin.inventory.products.create",
|
"admin.products.products.create",
|
||||||
"admin.sales.dashboard.allow",
|
"admin.sales.dashboard.allow",
|
||||||
"admin.contacts.customers.view",
|
"admin.contacts.customers.view",
|
||||||
"admin.sales.sales.view",
|
"admin.sales.sales.view",
|
||||||
@ -99,21 +99,21 @@
|
|||||||
"admin.billing.nomina.view",
|
"admin.billing.nomina.view",
|
||||||
"admin.billing.verify-cfdi.allow",
|
"admin.billing.verify-cfdi.allow",
|
||||||
"admin.contacts.suppliers.view",
|
"admin.contacts.suppliers.view",
|
||||||
"admin.inventory.orders.view",
|
"admin.purchase-orders.orders.view",
|
||||||
"admin.inventory.reception.view",
|
"admin.purchase-orders.reception.view",
|
||||||
"admin.inventory.materials.view",
|
"admin.purchase-orders.materials.view",
|
||||||
"admin.inventory.warehouse.view",
|
"admin.inventory.warehouse.view",
|
||||||
"admin.inventory.stock.view",
|
"admin.inventory.stock.view",
|
||||||
"admin.inventory.movements.view",
|
"admin.inventory.movements.view",
|
||||||
"admin.inventory.transfers.view",
|
"admin.inventory.transfers.view",
|
||||||
"admin.inventory.shipping-orders.view",
|
"admin.shipping.orders.view",
|
||||||
"admin.inventory.shipping-tracking.view",
|
"admin.shipping.tracking.view",
|
||||||
"admin.inventory.shipping-carriers.view",
|
"admin.shipping.carriers.view",
|
||||||
"admin.inventory.shipping-rates.view",
|
"admin.shipping.rates.view",
|
||||||
"admin.inventory.assets.view",
|
"admin.assets.assets.view",
|
||||||
"admin.inventory.asset-maintenance.view",
|
"admin.assets.maintenance.view",
|
||||||
"admin.inventory.asset-lifecycle.view",
|
"admin.assets.lifecycle.view",
|
||||||
"admin.inventory.asset-assignments.view",
|
"admin.assets.assignments.view",
|
||||||
"admin.projects.dashboard.view",
|
"admin.projects.dashboard.view",
|
||||||
"admin.projects.view",
|
"admin.projects.view",
|
||||||
"admin.projects.create",
|
"admin.projects.create",
|
||||||
@ -167,21 +167,21 @@
|
|||||||
"admin.rrhh.organization.view",
|
"admin.rrhh.organization.view",
|
||||||
"admin.inventory.product-categories.view",
|
"admin.inventory.product-categories.view",
|
||||||
"admin.inventory.product-catalogs.view",
|
"admin.inventory.product-catalogs.view",
|
||||||
"admin.inventory.products.view",
|
"admin.products.products.view",
|
||||||
"admin.inventory.products.create",
|
"admin.products.products.create",
|
||||||
"admin.contacts.suppliers.view",
|
"admin.contacts.suppliers.view",
|
||||||
"admin.contacts.suppliers.create",
|
"admin.contacts.suppliers.create",
|
||||||
"admin.inventory.warehouse.view",
|
"admin.inventory.warehouse.view",
|
||||||
"admin.inventory.orders.view",
|
"admin.purchase-orders.orders.view",
|
||||||
"admin.inventory.reception.view",
|
"admin.purchase-orders.reception.view",
|
||||||
"admin.inventory.materials.view",
|
"admin.purchase-orders.materials.view",
|
||||||
"admin.inventory.stock.view",
|
"admin.inventory.stock.view",
|
||||||
"admin.inventory.movements.view",
|
"admin.inventory.movements.view",
|
||||||
"admin.inventory.transfers.view",
|
"admin.inventory.transfers.view",
|
||||||
"admin.inventory.assets.view",
|
"admin.assets.assets.view",
|
||||||
"admin.inventory.asset-maintenance.view",
|
"admin.assets.maintenance.view",
|
||||||
"admin.inventory.asset-lifecycle.view",
|
"admin.assets.lifecycle.view",
|
||||||
"admin.inventory.asset-assignments.view"
|
"admin.assets.assignments.view"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"Administrador Web" : {
|
"Administrador Web" : {
|
||||||
@ -197,8 +197,8 @@
|
|||||||
"permissions" : [
|
"permissions" : [
|
||||||
"admin.inventory.product-categories.view",
|
"admin.inventory.product-categories.view",
|
||||||
"admin.inventory.product-catalogs.view",
|
"admin.inventory.product-catalogs.view",
|
||||||
"admin.inventory.products.view",
|
"admin.products.products.view",
|
||||||
"admin.inventory.products.create",
|
"admin.products.products.create",
|
||||||
"admin.inventory.warehouse.view",
|
"admin.inventory.warehouse.view",
|
||||||
"admin.inventory.stock.view",
|
"admin.inventory.stock.view",
|
||||||
"admin.inventory.movements.view",
|
"admin.inventory.movements.view",
|
||||||
@ -293,7 +293,7 @@
|
|||||||
"admin.attendance.absences.view",
|
"admin.attendance.absences.view",
|
||||||
"admin.inventory.product-categories.view",
|
"admin.inventory.product-categories.view",
|
||||||
"admin.inventory.product-catalogs.view",
|
"admin.inventory.product-catalogs.view",
|
||||||
"admin.inventory.products.view",
|
"admin.products.products.view",
|
||||||
"admin.contacts.customers.view",
|
"admin.contacts.customers.view",
|
||||||
"admin.sales.sales.view",
|
"admin.sales.sales.view",
|
||||||
"admin.sales.quotes.view",
|
"admin.sales.quotes.view",
|
||||||
@ -322,21 +322,21 @@
|
|||||||
"admin.billing.pagos.view",
|
"admin.billing.pagos.view",
|
||||||
"admin.billing.nomina.view",
|
"admin.billing.nomina.view",
|
||||||
"admin.contacts.suppliers.view",
|
"admin.contacts.suppliers.view",
|
||||||
"admin.inventory.orders.view",
|
"admin.purchase-orders.orders.view",
|
||||||
"admin.inventory.reception.view",
|
"admin.purchase-orders.reception.view",
|
||||||
"admin.inventory.materials.view",
|
"admin.purchase-orders.materials.view",
|
||||||
"admin.inventory.warehouse.view",
|
"admin.inventory.warehouse.view",
|
||||||
"admin.inventory.stock.view",
|
"admin.inventory.stock.view",
|
||||||
"admin.inventory.movements.view",
|
"admin.inventory.movements.view",
|
||||||
"admin.inventory.transfers.view",
|
"admin.inventory.transfers.view",
|
||||||
"admin.inventory.shipping-orders.view",
|
"admin.shipping.orders.view",
|
||||||
"admin.inventory.shipping-tracking.view",
|
"admin.shipping.tracking.view",
|
||||||
"admin.inventory.shipping-carriers.view",
|
"admin.shipping.carriers.view",
|
||||||
"admin.inventory.shipping-rates.view",
|
"admin.shipping.rates.view",
|
||||||
"admin.inventory.assets.view",
|
"admin.assets.assets.view",
|
||||||
"admin.inventory.asset-maintenance.view",
|
"admin.assets.maintenance.view",
|
||||||
"admin.inventory.asset-lifecycle.view",
|
"admin.assets.lifecycle.view",
|
||||||
"admin.inventory.asset-assignments.view",
|
"admin.assets.assignments.view",
|
||||||
"admin.projects.dashboard.view",
|
"admin.projects.dashboard.view",
|
||||||
"admin.projects.view",
|
"admin.projects.view",
|
||||||
"admin.projects.tasks.view",
|
"admin.projects.tasks.view",
|
||||||
@ -421,8 +421,8 @@
|
|||||||
"admin.attendance.absences.view",
|
"admin.attendance.absences.view",
|
||||||
"admin.inventory.product-categories.view",
|
"admin.inventory.product-categories.view",
|
||||||
"admin.inventory.product-catalogs.view",
|
"admin.inventory.product-catalogs.view",
|
||||||
"admin.inventory.products.view",
|
"admin.products.products.view",
|
||||||
"admin.inventory.products.create",
|
"admin.products.products.create",
|
||||||
"admin.sales.dashboard.allow",
|
"admin.sales.dashboard.allow",
|
||||||
"admin.contacts.customers.view",
|
"admin.contacts.customers.view",
|
||||||
"admin.contacts.customers.create",
|
"admin.contacts.customers.create",
|
||||||
@ -463,21 +463,21 @@
|
|||||||
"admin.billing.verify-cfdi.allow",
|
"admin.billing.verify-cfdi.allow",
|
||||||
"admin.contacts.suppliers.view",
|
"admin.contacts.suppliers.view",
|
||||||
"admin.contacts.suppliers.create",
|
"admin.contacts.suppliers.create",
|
||||||
"admin.inventory.orders.view",
|
"admin.purchase-orders.orders.view",
|
||||||
"admin.inventory.reception.view",
|
"admin.purchase-orders.reception.view",
|
||||||
"admin.inventory.materials.view",
|
"admin.purchase-orders.materials.view",
|
||||||
"admin.inventory.warehouse.view",
|
"admin.inventory.warehouse.view",
|
||||||
"admin.inventory.stock.view",
|
"admin.inventory.stock.view",
|
||||||
"admin.inventory.movements.view",
|
"admin.inventory.movements.view",
|
||||||
"admin.inventory.transfers.view",
|
"admin.inventory.transfers.view",
|
||||||
"admin.inventory.shipping-orders.view",
|
"admin.shipping.orders.view",
|
||||||
"admin.inventory.shipping-tracking.view",
|
"admin.shipping.tracking.view",
|
||||||
"admin.inventory.shipping-carriers.view",
|
"admin.shipping.carriers.view",
|
||||||
"admin.inventory.shipping-rates.view",
|
"admin.shipping.rates.view",
|
||||||
"admin.inventory.assets.view",
|
"admin.assets.assets.view",
|
||||||
"admin.inventory.asset-maintenance.view",
|
"admin.assets.maintenance.view",
|
||||||
"admin.inventory.asset-lifecycle.view",
|
"admin.assets.lifecycle.view",
|
||||||
"admin.inventory.asset-assignments.view",
|
"admin.assets.assignments.view",
|
||||||
"admin.projects.dashboard.view",
|
"admin.projects.dashboard.view",
|
||||||
"admin.projects.view",
|
"admin.projects.view",
|
||||||
"admin.projects.create",
|
"admin.projects.create",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
@ -12,18 +13,50 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('settings', function (Blueprint $table) {
|
Schema::create('settings', function (Blueprint $table) {
|
||||||
$table->mediumIncrements('id');
|
$table->smallIncrements('id');
|
||||||
|
|
||||||
|
// Clave del setting
|
||||||
$table->string('key')->index();
|
$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();
|
$table->unsignedMediumInteger('user_id')->nullable()->index();
|
||||||
|
|
||||||
// Unique constraints
|
// Valores segmentados por tipo para mejor rendimiento
|
||||||
$table->unique(['user_id', 'key']);
|
$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
|
// 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
|
* Carga los formatters dinámicamente
|
||||||
*/
|
*/
|
||||||
async loadFormatters() {
|
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 formatterPromises = Object.entries(formattersModules).map(async ([path, importer]) => {
|
||||||
const module = await importer();
|
const module = await importer();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { booleanStatusCatalog, statusIntBadgeBgCatalogCss, statusIntBadgeBgCatalog } from './globalConfig';
|
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) => {
|
export const userActionFormatter = (value, row, index) => {
|
||||||
if (!row.id) return '';
|
if (!row.id) return '';
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
// JS global variables
|
// JS global variables
|
||||||
window.config = {
|
window.config = {
|
||||||
colors: {
|
colors: {
|
||||||
primary: '#7367f0',
|
primary: '#155dfc',
|
||||||
secondary: '#808390',
|
secondary: '#808390',
|
||||||
success: '#28c76f',
|
success: '#28c76f',
|
||||||
info: '#00bad1',
|
info: '#00bad1',
|
||||||
@ -27,7 +27,7 @@ window.config = {
|
|||||||
borderColor: '#e6e6e8'
|
borderColor: '#e6e6e8'
|
||||||
},
|
},
|
||||||
colors_label: {
|
colors_label: {
|
||||||
primary: '#7367f029',
|
primary: '#155dfc29',
|
||||||
secondary: '#a8aaae29',
|
secondary: '#a8aaae29',
|
||||||
success: '#28c76f29',
|
success: '#28c76f29',
|
||||||
info: '#00cfe829',
|
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
|
this.setButtonLoadingState(saveButton, true); // Poner en estado de carga al botón anfitrión
|
||||||
|
|
||||||
// Enviar la solicitud de Livewire correspondiente al enviar el formulario
|
// 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) {
|
toggleButtonsState(buttons, isEnabled) {
|
||||||
buttons.forEach(button => {
|
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) {
|
initializeValidation(form) {
|
||||||
if (this.config.validationConfig) {
|
if (this.config.validationConfig) {
|
||||||
this.formValidationInstance = FormValidation.formValidation(form, this.config.validationConfig).on(
|
this.formValidationInstance = FormValidation.formValidation(
|
||||||
'core.form.valid',
|
form,
|
||||||
() => this.handleFormValid(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() {
|
reloadValidation() {
|
||||||
const form = document.querySelector(this.config.formSelector);
|
const form = document.querySelector(this.config.formSelector);
|
||||||
|
|
||||||
// Verificar si el formulario existe y si la validación está inicializada
|
|
||||||
if (form && this.formValidationInstance) {
|
if (form && this.formValidationInstance) {
|
||||||
try {
|
try {
|
||||||
// En lugar de destruir la validación, simplemente reiniciamos la validación.
|
setTimeout(() => {
|
||||||
this.formValidationInstance.resetForm(); // Resetear el formulario, limpiando los errores
|
this.formValidationInstance.resetForm(); // Limpiar errores
|
||||||
|
this.initializeValidation(form); // Reinicializar
|
||||||
|
|
||||||
// Reinicializar la validación con la configuración actual
|
// 🔁 Reconectar eventos de inputs y botones
|
||||||
this.initializeValidation(form);
|
this.initFormEvents(form);
|
||||||
|
}, 1);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error al reiniciar la validación:', 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.');
|
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
|
// scss-docs-start color-variables
|
||||||
$blue: #007bff !default;
|
$blue: #007bff !default;
|
||||||
$indigo: #6610f2 !default;
|
$indigo: #6610f2 !default;
|
||||||
$purple: #7367f0 !default;
|
$purple: #64748b !default;
|
||||||
$pink: #e83e8c !default;
|
$pink: #e83e8c !default;
|
||||||
$red: #ff4c51 !default;
|
$red: #ff4c51 !default;
|
||||||
$orange: #fd7e14 !default;
|
$orange: #fd7e14 !default;
|
||||||
|
@ -35,7 +35,7 @@ $grays: (
|
|||||||
// scss-docs-start color-variables
|
// scss-docs-start color-variables
|
||||||
$blue: #007bff !default;
|
$blue: #007bff !default;
|
||||||
$indigo: #6610f2 !default;
|
$indigo: #6610f2 !default;
|
||||||
$purple: #7367f0 !default;
|
$purple: #64748b !default;
|
||||||
$pink: #e83e8c !default;
|
$pink: #e83e8c !default;
|
||||||
$red: #ff4c51 !default;
|
$red: #ff4c51 !default;
|
||||||
$orange: #fd7e14 !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 'bootstrap-extended-dark';
|
||||||
@import 'components-dark';
|
@import 'components-dark';
|
||||||
@import 'colors-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 'bootstrap-extended';
|
||||||
@import 'components';
|
@import 'components';
|
||||||
@import 'colors';
|
@import 'colors';
|
||||||
|
|
||||||
|
$primary-color: #4a8c08;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@import './_theme/pages';
|
@import './_theme/pages';
|
||||||
@import './_theme/_theme';
|
@import './_theme/_theme';
|
||||||
|
|
||||||
$primary-color: #7367f0;
|
$primary-color: #155dfc;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: $card-bg;
|
background: $card-bg;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@import './_theme/pages';
|
@import './_theme/pages';
|
||||||
@import './_theme/_theme';
|
@import './_theme/_theme';
|
||||||
|
|
||||||
$primary-color: #7367f0;
|
$primary-color: #155dfc;
|
||||||
$body-bg: #f8f7fa;
|
$body-bg: #f8f7fa;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@import './_theme/pages';
|
@import './_theme/pages';
|
||||||
@import './_theme/_theme';
|
@import './_theme/_theme';
|
||||||
|
|
||||||
$primary-color: #7367f0;
|
$primary-color: #155dfc;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: $body-bg;
|
background: $body-bg;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@import './_theme/pages';
|
@import './_theme/pages';
|
||||||
@import './_theme/_theme';
|
@import './_theme/_theme';
|
||||||
|
|
||||||
$primary-color: #7367f0;
|
$primary-color: #155dfc;
|
||||||
$body-bg: #f8f7fa;
|
$body-bg: #f8f7fa;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@import './_theme/pages';
|
@import './_theme/pages';
|
||||||
@import './_theme/_theme';
|
@import './_theme/_theme';
|
||||||
|
|
||||||
$primary-color: #7367f0;
|
$primary-color: #155dfc;
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: $body-bg;
|
background: $body-bg;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@import './_theme/pages';
|
@import './_theme/pages';
|
||||||
@import './_theme/_theme';
|
@import './_theme/_theme';
|
||||||
|
|
||||||
$primary-color: #7367f0;
|
$primary-color: #155dfc;
|
||||||
$body-bg: #f8f7fa;
|
$body-bg: #f8f7fa;
|
||||||
|
|
||||||
body {
|
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({
|
new FormCustomListener({
|
||||||
buttonSelectors: ['.btn-save', '.btn-cancel', '.btn-reset'] // Selectores para botones
|
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 }) => {
|
Livewire.hook('morphed', ({ component }) => {
|
||||||
switch (component.name) {
|
switch (component.name) {
|
||||||
case 'mail-smtp-settings':
|
case 'sendmail-settings':
|
||||||
if (window.smtpSettingsForm) {
|
if (window.smtpSettingsForm) {
|
||||||
window.smtpSettingsForm.reload(); // Recarga el formulario sin destruir la instancia
|
window.smtpSettingsForm.reload(); // Recarga el formulario sin destruir la instancia
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
export default class SmtpSettingsForm {
|
export default class SmtpSettingsForm {
|
||||||
constructor(config = {}) {
|
constructor(config = {}) {
|
||||||
const defaultConfig = {
|
const defaultConfig = {
|
||||||
formSmtpSettingsSelector: '#mail-smtp-settings-card',
|
formSmtpSettingsSelector: '#sendmail-settings-card',
|
||||||
changeSmtpSettingsId: 'change_smtp_settings',
|
changeSmtpSettingsId: 'change_smtp_settings',
|
||||||
testSmtpConnectionButtonId: 'test_smtp_connection_button',
|
testSmtpConnectionButtonId: 'test_smtp_connection_button',
|
||||||
saveSmtpConnectionButtonId: 'save_smtp_connection_button',
|
saveSmtpConnectionButtonId: 'save_smtp_connection_button',
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
@import 'tailwindcss/components';
|
@import 'tailwindcss/components';
|
||||||
@import 'tailwindcss/utilities';
|
@import 'tailwindcss/utilities';
|
||||||
|
|
||||||
|
[x-cloak] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
.menu-horizontal-wrapper > .menu-inner > .menu-item:last-child {
|
.menu-horizontal-wrapper > .menu-inner > .menu-item:last-child {
|
||||||
padding-right: 70px;
|
padding-right: 70px;
|
||||||
}
|
}
|
||||||
|
@ -85,7 +85,7 @@ $authentication-1-inner-max-width: 460px !default;
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: -35px;
|
top: -35px;
|
||||||
left: -45px;
|
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 {
|
&:after {
|
||||||
@include light.media-breakpoint-down(sm) {
|
@include light.media-breakpoint-down(sm) {
|
||||||
@ -98,7 +98,7 @@ $authentication-1-inner-max-width: 460px !default;
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
bottom: -30px;
|
bottom: -30px;
|
||||||
right: -56px;
|
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="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
@livewire('cache-stats')
|
@livewire('vuexy-admin::cache-stats')
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
@livewire('session-stats')
|
@livewire('vuexy-admin::session-stats')
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
@livewire('cache-functions')
|
@livewire('vuexy-admin::cache-functions')
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@if($configCache['redisInUse'])
|
@if($configCache['redisInUse'])
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
@livewire('redis-stats')
|
@livewire('vuexy-admin::redis-stats')
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if($configCache['memcachedInUse'])
|
@if($configCache['memcachedInUse'])
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
@livewire('memcached-stats')
|
@livewire('vuexy-admin::memcached-stats')
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@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
|
@endphp
|
||||||
|
|
||||||
{{-- ============================ CHECKBOX CON INPUT GROUP ============================ --}}
|
{{-- ============================ CHECKBOX CON INPUT GROUP ============================ --}}
|
||||||
<div class="mb-4 {{ $parentClass }}">
|
<div class="mb-4 {{ $parentClass }} fv-row">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
<label for="{{ $checkboxId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
<label for="{{ $checkboxId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
||||||
@endif
|
@endif
|
||||||
|
@ -74,7 +74,7 @@
|
|||||||
|
|
||||||
@if ($switch)
|
@if ($switch)
|
||||||
{{-- ============================ MODO 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 }}">
|
<label class="switch {{ $switchTypeClass }} {{ $switchColorClass }} {{ $sizeClass }} {{ $labelClass }}">
|
||||||
<input
|
<input
|
||||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||||
@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
@else
|
@else
|
||||||
{{-- ============================ MODO CHECKBOX ============================ --}}
|
{{-- ============================ 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
|
<input
|
||||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||||
{{ $disabled ? 'disabled' : '' }}
|
{{ $disabled ? 'disabled' : '' }}
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
: ($image ? "<img src='{$image}' alt='{$title}' class='img-fluid rounded'>" : '');
|
: ($image ? "<img src='{$image}' alt='{$title}' class='img-fluid rounded'>" : '');
|
||||||
@endphp
|
@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 }}">
|
<label class="form-check-label custom-option-content" for="{{ $inputId }}">
|
||||||
<span class="custom-option-body">
|
<span class="custom-option-body">
|
||||||
{!! $visualContent !!}
|
{!! $visualContent !!}
|
||||||
|
@ -8,6 +8,8 @@
|
|||||||
'wireSubmit' => false, // Usar wire:submit.prevent
|
'wireSubmit' => false, // Usar wire:submit.prevent
|
||||||
'class' => '', // Clases adicionales para el formulario
|
'class' => '', // Clases adicionales para el formulario
|
||||||
'actionPosition' => 'bottom', // Posición de acciones: top, bottom, both, none
|
'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
|
@php
|
||||||
@ -28,8 +30,12 @@
|
|||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<form {{ $attributes->merge($formAttributes) }}>
|
<form {{ $attributes->merge($formAttributes) }}>
|
||||||
|
@if (!$whitOutId)
|
||||||
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="id" />
|
<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" />
|
<x-vuexy-admin::form.input :uid="$uniqueId" type="hidden" model="mode" />
|
||||||
|
@endif
|
||||||
@if ($mode !== 'delete' && in_array($actionPosition, ['top', 'both']))
|
@if ($mode !== 'delete' && in_array($actionPosition, ['top', 'both']))
|
||||||
<div class="notification-container mb-4"></div>
|
<div class="notification-container mb-4"></div>
|
||||||
<div class="form-actions mb-4">
|
<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
|
'mb0' => false, // Remover margen inferior
|
||||||
'parentClass' => '',
|
'parentClass' => '',
|
||||||
|
|
||||||
// Elementos opcionales antes/después del input
|
// Elementos de prefijo
|
||||||
'prefix' => null,
|
'prefix' => null,
|
||||||
'suffix' => null,
|
'prefixIcon' => null,
|
||||||
|
'icon' => null, // Alias para prefixIcon
|
||||||
|
'prefixClickable' => false,
|
||||||
|
'prefixAction' => null,
|
||||||
|
|
||||||
// Íconos dentro del input
|
// Elementos de sufijo
|
||||||
'icon' => null,
|
'suffix' => null,
|
||||||
'clickableIcon' => null,
|
'suffixIcon' => null,
|
||||||
|
'suffixClickable' => false,
|
||||||
|
'suffixAction' => null,
|
||||||
|
|
||||||
// Configuración especial
|
// Configuración especial
|
||||||
'phoneMode' => false, // "national", "international", "both"
|
'phoneMode' => false, // "national", "international", "both"
|
||||||
@ -47,6 +52,9 @@
|
|||||||
$inputId = $attributes->get('id', $name . '_' . $uid);
|
$inputId = $attributes->get('id', $name . '_' . $uid);
|
||||||
$type = $attributes->get('type', 'text');
|
$type = $attributes->get('type', 'text');
|
||||||
|
|
||||||
|
// Manejar el alias de icon a prefixIcon
|
||||||
|
$prefixIcon = $prefixIcon ?? $icon;
|
||||||
|
|
||||||
// **Definir formato de teléfono según `phoneMode`**
|
// **Definir formato de teléfono según `phoneMode`**
|
||||||
if ($phoneMode) {
|
if ($phoneMode) {
|
||||||
$type = 'tel';
|
$type = 'tel';
|
||||||
@ -120,37 +128,68 @@
|
|||||||
'id' => $inputId,
|
'id' => $inputId,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
])->class("form-control $sizeClass $alignClass $errorClass");
|
])->class("form-control $sizeClass $alignClass $errorClass");
|
||||||
|
|
||||||
|
// Verificar si se necesita el input-group
|
||||||
|
$hasAddons = $prefix || $prefixIcon || $suffix || $suffixIcon;
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
{{-- Estructura del Input --}}
|
{{-- Estructura del Input --}}
|
||||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }}">
|
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} fv-row">
|
||||||
{{-- Etiqueta --}}
|
{{-- Etiqueta --}}
|
||||||
@if ($label)
|
@if ($label)
|
||||||
<label for="{{ $inputId }}" class="form-label {{ $labelClass }}">{{ $label }}</label>
|
<label for="{{ $inputId }}" class="form-label {{ $labelClass }}">{{ $label }}</label>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
{{-- Input con Prefijos, Sufijos o Íconos --}}
|
{{-- Input con Prefijos o Sufijos --}}
|
||||||
@if ($prefix || $suffix || $icon || $clickableIcon)
|
@if ($hasAddons)
|
||||||
<div class="input-group input-group-merge">
|
<div class="input-group input-group-merge">
|
||||||
@isset($prefix)
|
{{-- Prefijo --}}
|
||||||
<span class="input-group-text">{{ $prefix }}</span>
|
@if ($prefix || $prefixIcon)
|
||||||
@endisset
|
@if ($prefixClickable)
|
||||||
|
<button type="button" class="input-group-text cursor-pointer" {{ $prefixAction ? "wire:click=$prefixAction" : '' }}>
|
||||||
@isset($icon)
|
@if ($prefixIcon)
|
||||||
<span class="input-group-text"><i class="{{ $icon }}"></i></span>
|
<i class="{{ $prefixIcon }}"></i>
|
||||||
@endisset
|
@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" : '' }} />
|
<input {!! $inputAttributes !!} {{ $livewireModel ? "wire:model=$livewireModel" : '' }} />
|
||||||
|
|
||||||
@isset($suffix)
|
{{-- Sufijo --}}
|
||||||
<span class="input-group-text">{{ $suffix }}</span>
|
@if ($suffix || $suffixIcon)
|
||||||
@endisset
|
@if ($suffixClickable)
|
||||||
|
<button type="button" class="input-group-text cursor-pointer" {{ $suffixAction ? "wire:click=$suffixAction" : '' }}>
|
||||||
@isset($clickableIcon)
|
@if ($suffixIcon)
|
||||||
<button type="button" class="input-group-text cursor-pointer">
|
<i class="{{ $suffixIcon }}"></i>
|
||||||
<i class="{{ $clickableIcon }}"></i>
|
@endif
|
||||||
|
@if ($suffix)
|
||||||
|
{{ $suffix }}
|
||||||
|
@endif
|
||||||
</button>
|
</button>
|
||||||
@endisset
|
@else
|
||||||
|
<span class="input-group-text">
|
||||||
|
@if ($suffixIcon)
|
||||||
|
<i class="{{ $suffixIcon }}"></i>
|
||||||
|
@endif
|
||||||
|
@if ($suffix)
|
||||||
|
{{ $suffix }}
|
||||||
|
@endif
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
{{-- Input Simple --}}
|
{{-- Input Simple --}}
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
{{-- ============================ RADIO BUTTON CON INPUT GROUP ============================ --}}
|
{{-- ============================ RADIO BUTTON CON INPUT GROUP ============================ --}}
|
||||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }}">
|
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} fv-row">
|
||||||
@if ($label)
|
@if ($label)
|
||||||
<label for="{{ $radioId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
<label for="{{ $radioId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
||||||
@endif
|
@endif
|
||||||
@ -86,7 +86,7 @@
|
|||||||
type="radio"
|
type="radio"
|
||||||
{{ $livewireRadio }}
|
{{ $livewireRadio }}
|
||||||
{{ $disabled ? 'disabled' : '' }}
|
{{ $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' }})"
|
onchange="toggleRadioInputState('{{ $radioId }}', '{{ $textInputId }}', {{ $focusOnCheck ? 'true' : 'false' }}, {{ $disableOnOffRadio ? 'true' : 'false' }})"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -72,7 +72,7 @@
|
|||||||
|
|
||||||
@if ($switch)
|
@if ($switch)
|
||||||
{{-- ============================ MODO 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 }}">
|
<label class="switch {{ $switchTypeClass }} {{ $switchColorClass }} {{ $sizeClass }} {{ $labelClass }}">
|
||||||
<input
|
<input
|
||||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||||
@ -102,7 +102,7 @@
|
|||||||
|
|
||||||
@else
|
@else
|
||||||
{{-- ============================ MODO RADIO ============================ --}}
|
{{-- ============================ 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
|
<input
|
||||||
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
{{ $livewireModel ? "wire:model=$livewireModel" : '' }}
|
||||||
{{ $disabled ? 'disabled' : '' }}
|
{{ $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
|
@endphp
|
||||||
|
|
||||||
{{-- ============================ TEXTAREA ============================ --}}
|
{{-- ============================ TEXTAREA ============================ --}}
|
||||||
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} {{ $alignClass }} {{ $floatingClass }}">
|
<div class="{{ $mb0 ? '' : 'mb-4' }} {{ $parentClass }} {{ $alignClass }} {{ $floatingClass }} fv-row">
|
||||||
@if (!$floating && $label)
|
@if (!$floating && $label)
|
||||||
<label for="{{ $inputId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
<label for="{{ $inputId }}" class="{{ $labelClass }}">{{ $label }}</label>
|
||||||
@endif
|
@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