first commit
This commit is contained in:
83
Livewire/AdminSettings/ApplicationSettings.php
Normal file
83
Livewire/AdminSettings/ApplicationSettings.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
84
Livewire/AdminSettings/GeneralSettings.php
Normal file
84
Livewire/AdminSettings/GeneralSettings.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use Koneko\VuexyAdmin\Services\AdminSettingsService;
|
||||
use Koneko\VuexyAdmin\Services\AdminTemplateService;
|
||||
|
||||
class GeneralSettings extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
private $targetNotify = "#general-settings-card .notification-container";
|
||||
|
||||
public $admin_title;
|
||||
public $admin_favicon_16x16,
|
||||
$admin_favicon_76x76,
|
||||
$admin_favicon_120x120,
|
||||
$admin_favicon_152x152,
|
||||
$admin_favicon_180x180,
|
||||
$admin_favicon_192x192;
|
||||
|
||||
public $upload_image_favicon;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadSettings();
|
||||
}
|
||||
|
||||
public function loadSettings($clearcache = false)
|
||||
{
|
||||
$this->upload_image_favicon = null;
|
||||
|
||||
$adminTemplateService = app(AdminTemplateService::class);
|
||||
|
||||
if ($clearcache) {
|
||||
$adminTemplateService->clearAdminVarsCache();
|
||||
}
|
||||
|
||||
// Obtener los valores de las configuraciones de la base de datos
|
||||
$settings = $adminTemplateService->getAdminVars();
|
||||
|
||||
$this->admin_title = $settings['title'];
|
||||
$this->admin_favicon_16x16 = $settings['favicon']['16x16'];
|
||||
$this->admin_favicon_76x76 = $settings['favicon']['76x76'];
|
||||
$this->admin_favicon_120x120 = $settings['favicon']['120x120'];
|
||||
$this->admin_favicon_152x152 = $settings['favicon']['152x152'];
|
||||
$this->admin_favicon_180x180 = $settings['favicon']['180x180'];
|
||||
$this->admin_favicon_192x192 = $settings['favicon']['192x192'];
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate([
|
||||
'admin_title' => 'required|string|max:255',
|
||||
'upload_image_favicon' => 'nullable|image|mimes:jpeg,png,jpg,svg,webp|max:20480',
|
||||
]);
|
||||
|
||||
$adminSettingsService = app(AdminSettingsService::class);
|
||||
|
||||
// Guardar título del sitio en configuraciones
|
||||
$adminSettingsService->updateSetting('admin_title', $this->admin_title);
|
||||
|
||||
// Procesar favicon si se ha cargado una imagen
|
||||
if ($this->upload_image_favicon) {
|
||||
$adminSettingsService->processAndSaveFavicon($this->upload_image_favicon);
|
||||
}
|
||||
|
||||
$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.general-settings');
|
||||
}
|
||||
}
|
118
Livewire/AdminSettings/InterfaceSettings.php
Normal file
118
Livewire/AdminSettings/InterfaceSettings.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
106
Livewire/AdminSettings/MailSenderResponseSettings.php
Normal file
106
Livewire/AdminSettings/MailSenderResponseSettings.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
175
Livewire/AdminSettings/MailSmtpSettings.php
Normal file
175
Livewire/AdminSettings/MailSmtpSettings.php
Normal file
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\AdminSettings;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Symfony\Component\Mailer\Transport;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Mime\Email;
|
||||
use Koneko\VuexyAdmin\Services\GlobalSettingsService;
|
||||
|
||||
|
||||
class MailSmtpSettings extends Component
|
||||
{
|
||||
private $targetNotify = "#mail-smtp-settings-card .notification-container";
|
||||
|
||||
public $change_smtp_settings,
|
||||
$host,
|
||||
$port,
|
||||
$encryption,
|
||||
$username,
|
||||
$password;
|
||||
|
||||
public $save_button_disabled;
|
||||
|
||||
protected $listeners = [
|
||||
'loadSettings',
|
||||
'testSmtpConnection',
|
||||
];
|
||||
|
||||
// the list of smtp_encryption values that can be stored in table
|
||||
const SMTP_ENCRYPTION_SSL = 'SSL';
|
||||
const SMTP_ENCRYPTION_TLS = 'TLS';
|
||||
const SMTP_ENCRYPTION_NONE = 'none';
|
||||
|
||||
public $encryption_options = [
|
||||
self::SMTP_ENCRYPTION_SSL => 'SSL (Secure Sockets Layer)',
|
||||
self::SMTP_ENCRYPTION_TLS => 'TLS (Transport Layer Security)',
|
||||
self::SMTP_ENCRYPTION_NONE => 'Sin encriptación (No recomendado)',
|
||||
];
|
||||
|
||||
public $rules = [
|
||||
[
|
||||
'host' => 'nullable|string|max:255',
|
||||
'port' => 'nullable|integer',
|
||||
'encryption' => 'nullable|string',
|
||||
'username' => 'nullable|string|max:255',
|
||||
'password' => 'nullable|string|max:255',
|
||||
],
|
||||
[
|
||||
'host.string' => 'El servidor SMTP debe ser una cadena de texto.',
|
||||
'host.max' => 'El servidor SMTP no puede exceder los 255 caracteres.',
|
||||
'port.integer' => 'El puerto SMTP debe ser un número entero.',
|
||||
'encryption.string' => 'El tipo de encriptación SMTP debe ser una cadena de texto.',
|
||||
'username.string' => 'El nombre de usuario SMTP debe ser una cadena de texto.',
|
||||
'username.max' => 'El nombre de usuario SMTP no puede exceder los 255 caracteres.',
|
||||
'password.string' => 'La contraseña SMTP debe ser una cadena de texto.',
|
||||
'password.max' => 'La contraseña SMTP no puede exceder los 255 caracteres.',
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
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->change_smtp_settings = false;
|
||||
$this->save_button_disabled = true;
|
||||
|
||||
$this->host = $settings['mailers']['smtp']['host'];
|
||||
$this->port = $settings['mailers']['smtp']['port'];
|
||||
$this->encryption = $settings['mailers']['smtp']['encryption'];
|
||||
$this->username = $settings['mailers']['smtp']['username'];
|
||||
$this->password = null;
|
||||
}
|
||||
|
||||
public function save()
|
||||
{
|
||||
$this->validate($this->rules[0]);
|
||||
|
||||
$globalSettingsService = app(GlobalSettingsService::class);
|
||||
|
||||
// Guardar título del App en configuraciones
|
||||
$globalSettingsService->updateSetting('mail.mailers.smtp.host', $this->host);
|
||||
$globalSettingsService->updateSetting('mail.mailers.smtp.port', $this->port);
|
||||
$globalSettingsService->updateSetting('mail.mailers.smtp.encryption', $this->encryption);
|
||||
$globalSettingsService->updateSetting('mail.mailers.smtp.username', $this->username);
|
||||
$globalSettingsService->updateSetting('mail.mailers.smtp.password', Crypt::encryptString($this->password));
|
||||
|
||||
$globalSettingsService->clearMailSystemConfigCache();
|
||||
|
||||
$this->loadSettings();
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han guardado los cambios en las configuraciones.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSmtpConnection()
|
||||
{
|
||||
// Validar los datos del formulario
|
||||
$this->validate($this->rules[0]);
|
||||
|
||||
try {
|
||||
// Verificar la conexión SMTP
|
||||
if ($this->validateSMTPConnection()) {
|
||||
$this->save_button_disabled = false;
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Conexión SMTP exitosa, se guardó los cambios exitosamente.',
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Captura y maneja errores de conexión SMTP
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'danger',
|
||||
message: 'Error en la conexión SMTP: ' . $e->getMessage(),
|
||||
delay: 15000 // Timeout personalizado
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function validateSMTPConnection()
|
||||
{
|
||||
$dsn = sprintf(
|
||||
'smtp://%s:%s@%s:%s?encryption=%s',
|
||||
urlencode($this->username), // Codificar nombre de usuario
|
||||
urlencode($this->password), // Codificar contraseña
|
||||
$this->host, // Host SMTP
|
||||
$this->port, // Puerto SMTP
|
||||
$this->encryption // Encriptación (tls o ssl)
|
||||
);
|
||||
|
||||
// Crear el transportador usando el DSN
|
||||
$transport = Transport::fromDsn($dsn);
|
||||
|
||||
// Crear el mailer con el transportador personalizado
|
||||
$mailer = new Mailer($transport);
|
||||
|
||||
// Enviar un correo de prueba
|
||||
$email = (new Email())
|
||||
->from($this->username) // Dirección de correo del remitente
|
||||
->to(env('MAIL_SANDBOX')) // Dirección de correo de destino
|
||||
->subject(Config::get('app.name') . ' - Correo de prueba')
|
||||
->text('Este es un correo de prueba para verificar la conexión SMTP.');
|
||||
|
||||
// Enviar el correo
|
||||
$mailer->send($email);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.admin-settings.mail-smtp-settings');
|
||||
}
|
||||
}
|
212
Livewire/Cache/CacheFunctions.php
Normal file
212
Livewire/Cache/CacheFunctions.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Cache;
|
||||
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class CacheFunctions extends Component
|
||||
{
|
||||
private $targetNotify = "#cache-functions-card .notification-container";
|
||||
|
||||
public $cacheCounts = [
|
||||
'general' => 0,
|
||||
'config' => 0,
|
||||
'routes' => 0,
|
||||
'views' => 0,
|
||||
'events' => 0,
|
||||
];
|
||||
|
||||
protected $listeners = [
|
||||
'reloadCacheFunctionsStatsEvent' => 'reloadCacheStats',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->reloadCacheStats(false);
|
||||
}
|
||||
|
||||
public function reloadCacheStats($notify = true)
|
||||
{
|
||||
$cacheDriver = config('cache.default'); // Obtiene el driver configurado para caché
|
||||
|
||||
// Caché General
|
||||
switch ($cacheDriver) {
|
||||
case 'memcached':
|
||||
try {
|
||||
$cacheStore = Cache::getStore()->getMemcached();
|
||||
$stats = $cacheStore->getStats();
|
||||
|
||||
$this->cacheCounts['general'] = array_sum(array_column($stats, 'curr_items')); // Total de claves en Memcached
|
||||
} catch (\Exception $e) {
|
||||
$this->cacheCounts['general'] = 'Error obteniendo datos de Memcached';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'redis':
|
||||
try {
|
||||
$prefix = config('cache.prefix'); // Asegúrate de agregar el sufijo correcto si es necesario
|
||||
$keys = Redis::connection('cache')->keys($prefix . '*');
|
||||
|
||||
$this->cacheCounts['general'] = count($keys); // Total de claves en Redis
|
||||
} catch (\Exception $e) {
|
||||
$this->cacheCounts['general'] = 'Error obteniendo datos de Redis';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'database':
|
||||
try {
|
||||
$this->cacheCounts['general'] = DB::table('cache')->count(); // Total de registros en la tabla de caché
|
||||
} catch (\Exception $e) {
|
||||
$this->cacheCounts['general'] = 'Error obteniendo datos de la base de datos';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'file':
|
||||
try {
|
||||
$cachePath = config('cache.stores.file.path');
|
||||
$files = glob($cachePath . '/*');
|
||||
|
||||
$this->cacheCounts['general'] = count($files);
|
||||
} catch (\Exception $e) {
|
||||
$this->cacheCounts['general'] = 'Error obteniendo datos de archivos';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->cacheCounts['general'] = 'Driver de caché no soportado';
|
||||
}
|
||||
|
||||
// Configuración
|
||||
$this->cacheCounts['config'] = file_exists(base_path('bootstrap/cache/config.php')) ? 1 : 0;
|
||||
|
||||
// Rutas
|
||||
$this->cacheCounts['routes'] = count(glob(base_path('bootstrap/cache/routes-*.php'))) > 0 ? 1 : 0;
|
||||
|
||||
// Vistas
|
||||
$this->cacheCounts['views'] = count(glob(storage_path('framework/views/*')));
|
||||
|
||||
// Configuración
|
||||
$this->cacheCounts['events'] = file_exists(base_path('bootstrap/cache/events.php')) ? 1 : 0;
|
||||
|
||||
if ($notify) {
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: 'success',
|
||||
message: 'Se han recargado los estadísticos de caché.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function clearLaravelCache()
|
||||
{
|
||||
Artisan::call('cache:clear');
|
||||
|
||||
sleep(1);
|
||||
|
||||
$this->response('Se han limpiado las cachés de la aplicación.', 'warning');
|
||||
}
|
||||
|
||||
public function clearConfigCache()
|
||||
{
|
||||
Artisan::call('config:clear');
|
||||
|
||||
$this->response('Se ha limpiado la cache de la configuración de Laravel.', 'warning');
|
||||
}
|
||||
|
||||
public function configCache()
|
||||
{
|
||||
Artisan::call('config:cache');
|
||||
}
|
||||
|
||||
public function clearRouteCache()
|
||||
{
|
||||
Artisan::call('route:clear');
|
||||
|
||||
$this->response('Se han limpiado las rutas de Laravel.', 'warning');
|
||||
}
|
||||
|
||||
public function cacheRoutes()
|
||||
{
|
||||
Artisan::call('route:cache');
|
||||
}
|
||||
|
||||
public function clearViewCache()
|
||||
{
|
||||
Artisan::call('view:clear');
|
||||
|
||||
$this->response('Se han limpiado las vistas de Laravel.', 'warning');
|
||||
}
|
||||
|
||||
public function cacheViews()
|
||||
{
|
||||
Artisan::call('view:cache');
|
||||
|
||||
$this->response('Se han cacheado las vistas de Laravel.');
|
||||
}
|
||||
|
||||
public function clearEventCache()
|
||||
{
|
||||
Artisan::call('event:clear');
|
||||
|
||||
$this->response('Se han limpiado los eventos de Laravel.', 'warning');
|
||||
}
|
||||
|
||||
public function cacheEvents()
|
||||
{
|
||||
Artisan::call('event:cache');
|
||||
|
||||
$this->response('Se han cacheado los eventos de Laravel.');
|
||||
}
|
||||
|
||||
public function optimizeClear()
|
||||
{
|
||||
Artisan::call('optimize:clear');
|
||||
|
||||
$this->response('Se han optimizado todos los cachés de Laravel.');
|
||||
}
|
||||
|
||||
public function resetPermissionCache()
|
||||
{
|
||||
Artisan::call('permission:cache-reset');
|
||||
|
||||
$this->response('Se han limpiado los cachés de permisos.', 'warning');
|
||||
}
|
||||
|
||||
public function clearResetTokens()
|
||||
{
|
||||
Artisan::call('auth:clear-resets');
|
||||
|
||||
$this->response('Se han limpiado los tokens de reseteo de contraseña.', 'warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* Genera una respuesta estandarizada.
|
||||
*/
|
||||
private function response(string $message, string $type = 'success'): void
|
||||
{
|
||||
$this->reloadCacheStats(false);
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $type,
|
||||
message: $message,
|
||||
);
|
||||
|
||||
$this->dispatch('reloadCacheStatsEvent', notify: false);
|
||||
$this->dispatch('reloadSessionStatsEvent', notify: false);
|
||||
$this->dispatch('reloadRedisStatsEvent', notify: false);
|
||||
$this->dispatch('reloadMemcachedStatsEvent', notify: false);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.cache.cache-functions');
|
||||
}
|
||||
}
|
65
Livewire/Cache/CacheStats.php
Normal file
65
Livewire/Cache/CacheStats.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Cache;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\CacheConfigService;
|
||||
use Koneko\VuexyAdmin\Services\CacheManagerService;
|
||||
|
||||
class CacheStats extends Component
|
||||
{
|
||||
private $targetNotify = "#cache-stats-card .notification-container";
|
||||
|
||||
public $cacheConfig = [];
|
||||
public $cacheStats = [];
|
||||
|
||||
protected $listeners = ['reloadCacheStatsEvent' => 'reloadCacheStats'];
|
||||
|
||||
public function mount(CacheConfigService $cacheConfigService)
|
||||
{
|
||||
$this->cacheConfig = $cacheConfigService->getConfig();
|
||||
|
||||
$this->reloadCacheStats(false);
|
||||
}
|
||||
|
||||
public function reloadCacheStats($notify = true)
|
||||
{
|
||||
$cacheManagerService = new CacheManagerService();
|
||||
|
||||
$this->cacheStats = $cacheManagerService->getCacheStats();
|
||||
|
||||
if ($notify) {
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $this->cacheStats['status'],
|
||||
message: $this->cacheStats['message']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache()
|
||||
{
|
||||
$cacheManagerService = new CacheManagerService();
|
||||
|
||||
$message = $cacheManagerService->clearCache();
|
||||
|
||||
$this->reloadCacheStats(false);
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $message['status'],
|
||||
message: $message['message'],
|
||||
);
|
||||
|
||||
$this->dispatch('reloadRedisStatsEvent', notify: false);
|
||||
$this->dispatch('reloadMemcachedStatsEvent', notify: false);
|
||||
$this->dispatch('reloadCacheFunctionsStatsEvent', notify: false);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.cache.cache-stats');
|
||||
}
|
||||
}
|
64
Livewire/Cache/MemcachedStats.php
Normal file
64
Livewire/Cache/MemcachedStats.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Cache;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\CacheManagerService;
|
||||
|
||||
class MemcachedStats extends Component
|
||||
{
|
||||
private $driver = 'memcached';
|
||||
private $targetNotify = "#memcached-stats-card .notification-container";
|
||||
|
||||
public $memcachedStats = [];
|
||||
|
||||
protected $listeners = ['reloadMemcachedStatsEvent' => 'reloadCacheStats'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->reloadCacheStats(false);
|
||||
}
|
||||
|
||||
public function reloadCacheStats($notify = true)
|
||||
{
|
||||
$cacheManagerService = new CacheManagerService($this->driver);
|
||||
|
||||
$memcachedStats = $cacheManagerService->getMemcachedStats();
|
||||
|
||||
$this->memcachedStats = $memcachedStats['info'];
|
||||
|
||||
if ($notify) {
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $memcachedStats['status'],
|
||||
message: $memcachedStats['message']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache()
|
||||
{
|
||||
$cacheManagerService = new CacheManagerService($this->driver);
|
||||
|
||||
$message = $cacheManagerService->clearCache();
|
||||
|
||||
$this->reloadCacheStats(false);
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $message['status'],
|
||||
message: $message['message'],
|
||||
);
|
||||
|
||||
$this->dispatch('reloadCacheStatsEvent', notify: false);
|
||||
$this->dispatch('reloadSessionStatsEvent', notify: false);
|
||||
$this->dispatch('reloadCacheFunctionsStatsEvent', notify: false);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.cache.memcached-stats');
|
||||
}
|
||||
}
|
64
Livewire/Cache/RedisStats.php
Normal file
64
Livewire/Cache/RedisStats.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Cache;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\CacheManagerService;
|
||||
|
||||
class RedisStats extends Component
|
||||
{
|
||||
private $driver = 'redis';
|
||||
private $targetNotify = "#redis-stats-card .notification-container";
|
||||
|
||||
public $redisStats = [];
|
||||
|
||||
protected $listeners = ['reloadRedisStatsEvent' => 'reloadCacheStats'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->reloadCacheStats(false);
|
||||
}
|
||||
|
||||
public function reloadCacheStats($notify = true)
|
||||
{
|
||||
$cacheManagerService = new CacheManagerService($this->driver);
|
||||
|
||||
$redisStats = $cacheManagerService->getRedisStats();
|
||||
|
||||
$this->redisStats = $redisStats['info'];
|
||||
|
||||
if ($notify) {
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $redisStats['status'],
|
||||
message: $redisStats['message']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache()
|
||||
{
|
||||
$cacheManagerService = new CacheManagerService($this->driver);
|
||||
|
||||
$message = $cacheManagerService->clearCache();
|
||||
|
||||
$this->reloadCacheStats(false);
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $message['status'],
|
||||
message: $message['message'],
|
||||
);
|
||||
|
||||
$this->dispatch('reloadCacheStatsEvent', notify: false);
|
||||
$this->dispatch('reloadSessionStatsEvent', notify: false);
|
||||
$this->dispatch('reloadCacheFunctionsStatsEvent', notify: false);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.cache.redis-stats');
|
||||
}
|
||||
}
|
63
Livewire/Cache/SessionStats.php
Normal file
63
Livewire/Cache/SessionStats.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Cache;
|
||||
|
||||
use Livewire\Component;
|
||||
use Koneko\VuexyAdmin\Services\CacheConfigService;
|
||||
use Koneko\VuexyAdmin\Services\SessionManagerService;
|
||||
|
||||
class SessionStats extends Component
|
||||
{
|
||||
private $targetNotify = "#session-stats-card .notification-container";
|
||||
|
||||
public $cacheConfig = [];
|
||||
public $sessionStats = [];
|
||||
|
||||
protected $listeners = ['reloadSessionStatsEvent' => 'reloadSessionStats'];
|
||||
|
||||
public function mount(CacheConfigService $cacheConfigService)
|
||||
{
|
||||
$this->cacheConfig = $cacheConfigService->getConfig();
|
||||
$this->reloadSessionStats(false);
|
||||
}
|
||||
|
||||
public function reloadSessionStats($notify = true)
|
||||
{
|
||||
$sessionManagerService = new SessionManagerService();
|
||||
|
||||
$this->sessionStats = $sessionManagerService->getSessionStats();
|
||||
|
||||
if ($notify) {
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $this->sessionStats['status'],
|
||||
message: $this->sessionStats['message']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function clearSessions()
|
||||
{
|
||||
$sessionManagerService = new SessionManagerService();
|
||||
|
||||
$message = $sessionManagerService->clearSessions();
|
||||
|
||||
$this->reloadSessionStats(false);
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $this->targetNotify,
|
||||
type: $message['status'],
|
||||
message: $message['message'],
|
||||
);
|
||||
|
||||
$this->dispatch('reloadRedisStatsEvent', notify: false);
|
||||
$this->dispatch('reloadMemcachedStatsEvent', notify: false);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.cache.session-stats');
|
||||
}
|
||||
}
|
515
Livewire/Form/AbstractFormComponent.php
Normal file
515
Livewire/Form/AbstractFormComponent.php
Normal file
@ -0,0 +1,515 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Form;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Class AbstractFormComponent
|
||||
*
|
||||
* Clase base y abstracta para la creación de formularios con Livewire.
|
||||
* Proporciona métodos y un flujo general para manejar operaciones CRUD
|
||||
* (creación, edición y eliminación), validaciones, notificaciones y
|
||||
* administración de errores en un entorno transaccional.
|
||||
*
|
||||
* @package Koneko\VuexyAdmin\Livewire\Form
|
||||
*/
|
||||
abstract class AbstractFormComponent extends Component
|
||||
{
|
||||
/**
|
||||
* Identificador único del formulario, útil para distinguir múltiples instancias.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $uniqueId;
|
||||
|
||||
/**
|
||||
* Modo actual del formulario: puede ser 'create', 'edit' o 'delete'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $mode;
|
||||
|
||||
/**
|
||||
* Texto que se mostrará en el botón de envío. Se adapta
|
||||
* automáticamente en función del modo actual (crear, editar o eliminar).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $btnSubmitText;
|
||||
|
||||
/**
|
||||
* ID del registro que se está editando o eliminando.
|
||||
* Si el formulario está en modo 'create', puede ser null.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Nombre de la etiqueta para generar Componentes
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $tagName;
|
||||
|
||||
/**
|
||||
* Nombre de la columna que contiene el nombre del registro.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $columnNameLabel;
|
||||
|
||||
/**
|
||||
* Nombre singular del modelo
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $singularName;
|
||||
|
||||
/*
|
||||
* Nombre del identificador del Canvas
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $offcanvasId;
|
||||
|
||||
/*
|
||||
* Nombre del identificador del Form
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $formId;
|
||||
|
||||
// ======================================================================
|
||||
// MÉTODOS ABSTRACTOS
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Retorna la clase (namespace) del modelo Eloquent asociado al formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function model(): string;
|
||||
|
||||
/**
|
||||
* Retorna las reglas de validación de forma dinámica, dependiendo del modo del formulario.
|
||||
*
|
||||
* @param string $mode El modo actual del formulario (por ejemplo, 'create', 'edit' o 'delete').
|
||||
* @return array Reglas de validación (similares a las usadas en un Request de Laravel).
|
||||
*/
|
||||
abstract protected function dynamicRules(string $mode): array;
|
||||
|
||||
/**
|
||||
* Inicializa los datos del formulario con base en el registro (si existe)
|
||||
* y en el modo actual. Útil para prellenar campos en modo 'edit'.
|
||||
*
|
||||
* @param mixed $record El registro encontrado, o null si se crea uno nuevo.
|
||||
* @param string $mode El modo actual del formulario.
|
||||
* @return void
|
||||
*/
|
||||
abstract protected function initializeFormData(mixed $record, string $mode): void;
|
||||
|
||||
/**
|
||||
* Prepara los datos ya validados para ser guardados en base de datos.
|
||||
* Permite, por ejemplo, castear valores o limpiar ciertos campos.
|
||||
*
|
||||
* @param array $validatedData Datos que ya pasaron la validación.
|
||||
* @return array Datos listos para el almacenamiento (por ejemplo, en create o update).
|
||||
*/
|
||||
abstract protected function prepareData(array $validatedData): array;
|
||||
|
||||
/**
|
||||
* Define los contenedores de destino para las notificaciones.
|
||||
*
|
||||
* Retorna un array con keys como 'form', 'index', etc., y sus
|
||||
* valores deben ser selectores o identificadores en la vista, donde
|
||||
* se inyectarán las notificaciones.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function targetNotifies(): array;
|
||||
|
||||
/**
|
||||
* Retorna la ruta de la vista Blade correspondiente a este formulario.
|
||||
*
|
||||
* Por ejemplo: 'package::livewire.some-form'.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function viewPath(): string;
|
||||
|
||||
// ======================================================================
|
||||
// MÉTODOS DE VALIDACIÓN
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Retorna un array que define nombres de atributos personalizados para los mensajes de validación.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function attributes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna un array con mensajes de validación personalizados.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function messages(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// INICIALIZACIÓN Y CICLO DE VIDA
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Método que se ejecuta al montar (instanciar) el componente Livewire.
|
||||
* Inicializa propiedades clave como el $mode, $id, $uniqueId, el texto
|
||||
* del botón de envío, y carga datos del registro si no es un 'create'.
|
||||
*
|
||||
* @param string $mode Modo del formulario: 'create', 'edit' o 'delete'.
|
||||
* @param int|null $id ID del registro a editar/eliminar (o null para crear).
|
||||
* @return void
|
||||
*/
|
||||
public function mount(string $mode = 'create', mixed $id = null): void
|
||||
{
|
||||
$this->uniqueId = uniqid();
|
||||
$this->mode = $mode;
|
||||
$this->id = $id;
|
||||
|
||||
$model = new ($this->model());
|
||||
|
||||
$this->tagName = $model->tagName;
|
||||
$this->columnNameLabel = $model->columnNameLabel;
|
||||
$this->singularName = $model->singularName;
|
||||
$this->formId = Str::camel($model->tagName) .'Form';
|
||||
|
||||
$this->setBtnSubmitText();
|
||||
|
||||
if ($this->mode !== 'create' && $this->id) {
|
||||
// Si no es modo 'create', cargamos el registro desde la BD
|
||||
$record = $this->model()::findOrFail($this->id);
|
||||
|
||||
$this->initializeFormData($record, $mode);
|
||||
|
||||
} else {
|
||||
// Modo 'create', o sin ID: iniciamos datos vacíos
|
||||
$this->initializeFormData(null, $mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configura el texto del botón principal de envío, basado en la propiedad $mode.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setBtnSubmitText(): void
|
||||
{
|
||||
$this->btnSubmitText = match ($this->mode) {
|
||||
'create' => 'Crear ' . $this->singularName(),
|
||||
'edit' => 'Guardar cambios',
|
||||
'delete' => 'Eliminar ' . $this->singularName(),
|
||||
default => 'Enviar'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna el "singularName" definido en el modelo asociado.
|
||||
* Permite también decidir si se devuelve con la primera letra en mayúscula
|
||||
* o en minúscula.
|
||||
*
|
||||
* @param string $type Puede ser 'uppercase' o 'lowercase'. Por defecto, 'lowercase'.
|
||||
* @return string Nombre en singular del modelo, formateado.
|
||||
*/
|
||||
private function singularName($type = 'lowercase'): string
|
||||
{
|
||||
/** @var Model $model */
|
||||
$model = new ($this->model());
|
||||
|
||||
return $type === 'uppercase'
|
||||
? ucfirst($model->singularName)
|
||||
: lcfirst($model->singularName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Método del ciclo de vida de Livewire que se llama en cada hidratación.
|
||||
* Puedes disparar eventos o manejar lógica que suceda en cada request
|
||||
* una vez que Livewire 'rehidrate' el componente en el servidor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function hydrate(): void
|
||||
{
|
||||
$this->dispatch($this->dispatches()['on-hydrate']);
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// OPERACIONES CRUD
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Método principal de envío del formulario (submit). Gestiona los flujos
|
||||
* de crear, editar o eliminar un registro dentro de una transacción de BD.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onSubmit(): void
|
||||
{
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
if ($this->mode === 'delete') {
|
||||
$this->delete();
|
||||
} else {
|
||||
$this->save();
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
DB::rollBack();
|
||||
$this->handleValidationException($e);
|
||||
|
||||
} catch (QueryException $e) {
|
||||
DB::rollBack();
|
||||
$this->handleDatabaseException($e);
|
||||
|
||||
} catch (ModelNotFoundException $e) {
|
||||
DB::rollBack();
|
||||
$this->handleException('danger', 'Registro no encontrado.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack();
|
||||
$this->handleException('danger', 'Error al eliminar el registro: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crea o actualiza un registro en la base de datos,
|
||||
* aplicando validaciones y llamadas a hooks antes y después de guardar.
|
||||
*
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function save(): void
|
||||
{
|
||||
// Validamos los datos, con posibles atributos y mensajes personalizados
|
||||
$validatedData = $this->validate(
|
||||
$this->dynamicRules($this->mode),
|
||||
$this->messages(),
|
||||
$this->attributes()
|
||||
);
|
||||
|
||||
// Hook previo (por referencia)
|
||||
$this->beforeSave($validatedData);
|
||||
|
||||
// Ajustamos/convertimos los datos finales
|
||||
$data = $this->prepareData($validatedData);
|
||||
$record = $this->model()::updateOrCreate(['id' => $this->id], $data);
|
||||
|
||||
// Hook posterior
|
||||
$this->afterSave($record);
|
||||
|
||||
// Notificamos éxito
|
||||
$this->handleSuccess('success', $this->singularName('uppercase') . " guardado correctamente.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina un registro de la base de datos (modo 'delete'),
|
||||
* aplicando validaciones y hooks antes y después de la eliminación.
|
||||
*
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function delete(): void
|
||||
{
|
||||
$this->validate($this->dynamicRules('delete', $this->messages(), $this->attributes()));
|
||||
|
||||
$record = $this->model()::findOrFail($this->id);
|
||||
|
||||
// Hook antes de la eliminación
|
||||
$this->beforeDelete($record);
|
||||
|
||||
$record->delete();
|
||||
|
||||
// Hook después de la eliminación
|
||||
$this->afterDelete($record);
|
||||
|
||||
$this->handleSuccess('warning', $this->singularName('uppercase') . " eliminado.");
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// HOOKS DE ACCIONES
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta antes de guardar o actualizar un registro.
|
||||
* Puede usarse para ajustar o limpiar datos antes de la operación en base de datos.
|
||||
*
|
||||
* @param array $data Datos validados que se van a guardar.
|
||||
* Se pasa por referencia para permitir cambios.
|
||||
* @return void
|
||||
*/
|
||||
protected function beforeSave(array &$data): void {}
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta después de guardar o actualizar un registro.
|
||||
* Puede usarse para acciones como disparar eventos, notificaciones a otros sistemas, etc.
|
||||
*
|
||||
* @param mixed $record Instancia del modelo recién creado o actualizado.
|
||||
* @return void
|
||||
*/
|
||||
protected function afterSave($record): void {}
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta antes de eliminar un registro.
|
||||
* Puede emplearse para validaciones adicionales o limpieza de datos relacionados.
|
||||
*
|
||||
* @param mixed $record Instancia del modelo que se eliminará.
|
||||
* @return void
|
||||
*/
|
||||
protected function beforeDelete($record): void {}
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta después de eliminar un registro.
|
||||
* Útil para operaciones finales, como remover archivos relacionados o
|
||||
* disparar un evento de "elemento eliminado".
|
||||
*
|
||||
* @param mixed $record Instancia del modelo que se acaba de eliminar.
|
||||
* @return void
|
||||
*/
|
||||
protected function afterDelete($record): void {}
|
||||
|
||||
// ======================================================================
|
||||
// MANEJO DE VALIDACIONES Y ERRORES
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Maneja las excepciones de validación (ValidationException).
|
||||
* Asigna los errores al error bag de Livewire y muestra notificaciones.
|
||||
*
|
||||
* @param ValidationException $e Excepción de validación.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleValidationException(ValidationException $e): void
|
||||
{
|
||||
$this->setErrorBag($e->validator->errors());
|
||||
$this->handleException('danger', 'Error en la validación de los datos.');
|
||||
$this->dispatch($this->dispatches()['on-failed-validation']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja las excepciones de base de datos (QueryException).
|
||||
* Incluye casos especiales para claves foráneas y duplicadas.
|
||||
*
|
||||
* @param QueryException $e Excepción de consulta a la base de datos.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleDatabaseException(QueryException $e): void
|
||||
{
|
||||
$errorMessage = match ($e->errorInfo[1]) {
|
||||
1452 => "Una clave foránea no es válida.",
|
||||
1062 => $this->extractDuplicateField($e->getMessage()),
|
||||
1451 => "No se puede eliminar el registro porque está en uso.",
|
||||
default => env('APP_DEBUG') ? $e->getMessage() : "Error inesperado en la base de datos.",
|
||||
};
|
||||
|
||||
$this->handleException('danger', $errorMessage, 'form', 120000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja excepciones o errores generales, mostrando una notificación al usuario.
|
||||
*
|
||||
* @param string $type Tipo de notificación (por ejemplo, 'success', 'warning', 'danger').
|
||||
* @param string $message Mensaje que se mostrará en la notificación.
|
||||
* @param string $target Objetivo/área donde se mostrará la notificación ('form', 'index', etc.).
|
||||
* @param int $delay Tiempo en milisegundos que la notificación permanecerá visible.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleException($type, $message, $target = 'form', $delay = 9000): void
|
||||
{
|
||||
$this->dispatchNotification($type, $message, $target, $delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrae el campo duplicado de un mensaje de error MySQL, para mostrar un mensaje amigable.
|
||||
*
|
||||
* @param string $errorMessage Mensaje de error completo de la base de datos.
|
||||
* @return string Mensaje simplificado indicando cuál campo está duplicado.
|
||||
*/
|
||||
private function extractDuplicateField($errorMessage): string
|
||||
{
|
||||
preg_match("/for key 'unique_(.*?)'/", $errorMessage, $matches);
|
||||
|
||||
return isset($matches[1])
|
||||
? "El valor ingresado para '" . str_replace('_', ' ', $matches[1]) . "' ya está en uso."
|
||||
: "Ya existe un registro con este valor.";
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// NOTIFICACIONES Y REDIRECCIONAMIENTOS
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Maneja el flujo de notificación y redirección cuando una operación
|
||||
* (guardar, eliminar) finaliza satisfactoriamente.
|
||||
*
|
||||
* @param string $type Tipo de notificación ('success', 'warning', etc.).
|
||||
* @param string $message Mensaje a mostrar.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleSuccess($type, $message): void
|
||||
{
|
||||
$this->dispatchNotification($type, $message, 'index');
|
||||
$this->redirectRoute($this->getRedirectRoute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía una notificación al navegador (mediante eventos de Livewire)
|
||||
* indicando el tipo, el mensaje y el destino donde debe visualizarse.
|
||||
*
|
||||
* @param string $type Tipo de notificación (success, danger, etc.).
|
||||
* @param string $message Mensaje de la notificación.
|
||||
* @param string $target Destino para mostrarla ('form', 'index', etc.).
|
||||
* @param int $delay Duración de la notificación en milisegundos.
|
||||
* @return void
|
||||
*/
|
||||
protected function dispatchNotification($type, $message, $target = 'form', $delay = 9000): void
|
||||
{
|
||||
$this->dispatch(
|
||||
$target == 'index' ? 'store-notification' : 'notification',
|
||||
target: $target === 'index' ? $this->targetNotifies()['index'] : $this->targetNotifies()['form'],
|
||||
type: $type,
|
||||
message: $message,
|
||||
delay: $delay
|
||||
);
|
||||
}
|
||||
|
||||
// ======================================================================
|
||||
// RENDERIZACIÓN
|
||||
// ======================================================================
|
||||
|
||||
/**
|
||||
* Renderiza la vista Blade asociada a este componente.
|
||||
* Retorna un objeto Illuminate\View\View.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view($this->viewPath());
|
||||
}
|
||||
}
|
667
Livewire/Form/AbstractFormOffCanvasComponent.php
Normal file
667
Livewire/Form/AbstractFormOffCanvasComponent.php
Normal file
@ -0,0 +1,667 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Form;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Clase base abstracta para manejar formularios de tipo Off-Canvas con Livewire.
|
||||
*
|
||||
* Esta clase proporciona métodos reutilizables para operaciones CRUD, validaciones dinámicas,
|
||||
* manejo de transacciones en base de datos y eventos de Livewire.
|
||||
*
|
||||
* @package Koneko\VuexyAdmin\Livewire\Form
|
||||
*/
|
||||
abstract class AbstractFormOffCanvasComponent extends Component
|
||||
{
|
||||
/**
|
||||
* Identificador único del formulario, usado para evitar conflictos en instancias múltiples.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $uniqueId;
|
||||
|
||||
/**
|
||||
* Modo actual del formulario: puede ser 'create', 'edit' o 'delete'.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $mode;
|
||||
|
||||
/**
|
||||
* ID del registro que se está editando o eliminando.
|
||||
*
|
||||
* @var int|null
|
||||
*/
|
||||
public $id;
|
||||
|
||||
/**
|
||||
* Valores por defecto para los campos del formulario,
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $defaultValues;
|
||||
|
||||
/**
|
||||
* Nombre de la etiqueta para generar Componentes
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $tagName;
|
||||
|
||||
/**
|
||||
* Nombre de la columna que contiene el nombre del registro.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $columnNameLabel;
|
||||
|
||||
/**
|
||||
* Nombre singular del modelo
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $singularName;
|
||||
|
||||
/*
|
||||
* Nombre del identificador del Canvas
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $offcanvasId;
|
||||
|
||||
/*
|
||||
* Nombre del identificador del Form
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $formId;
|
||||
|
||||
/**
|
||||
* Campo que se debe enfocar cuando se abra el formulario.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $focusOnOpen;
|
||||
|
||||
/**
|
||||
* Indica si se desea confirmar la eliminación del registro.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $confirmDeletion = false;
|
||||
|
||||
/**
|
||||
* Indica si se ha producido un error de validación.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $validationError = false;
|
||||
|
||||
/*
|
||||
* Indica si se ha procesado correctamente el formulario.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $successProcess = false;
|
||||
|
||||
/**
|
||||
* Campos que deben ser casteados a tipos específicos.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [];
|
||||
|
||||
// ===================== MÉTODOS ABSTRACTOS =====================
|
||||
|
||||
/**
|
||||
* Define el modelo Eloquent asociado con el formulario.
|
||||
*
|
||||
* @return 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.
|
||||
*
|
||||
* @param string $mode Modo actual del formulario ('create', 'edit', 'delete').
|
||||
* @return array<string, mixed> Reglas de validación.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @return string Ruta de la vista Blade.
|
||||
*/
|
||||
abstract protected function viewPath(): string;
|
||||
|
||||
// ===================== VALIDACIONES =====================
|
||||
|
||||
protected function attributes(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function messages(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
// ===================== INICIALIZACIÓN DEL COMPONENTE =====================
|
||||
|
||||
/**
|
||||
* Se ejecuta cuando el componente se monta por primera vez.
|
||||
*
|
||||
* Inicializa propiedades y carga datos iniciales.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
$this->uniqueId = uniqid();
|
||||
|
||||
$model = new ($this->model());
|
||||
|
||||
$this->tagName = $model->tagName;
|
||||
$this->columnNameLabel = $model->columnNameLabel;
|
||||
$this->singularName = $model->singularName;
|
||||
$this->offcanvasId = 'offcanvas' . ucfirst(Str::camel($model->tagName));
|
||||
$this->formId = Str::camel($model->tagName) .'Form';
|
||||
$this->focusOnOpen = "{$this->focusOnOpen()}_{$this->uniqueId}";
|
||||
|
||||
$this->loadDefaults();
|
||||
$this->loadOptions();
|
||||
}
|
||||
|
||||
// ===================== INICIALIZACIÓN Y CONFIGURACIÓN =====================
|
||||
|
||||
/**
|
||||
* Devuelve los valores por defecto para los campos del formulario.
|
||||
*
|
||||
* @return array<string, mixed> Valores por defecto.
|
||||
*/
|
||||
private function loadDefaults(): void
|
||||
{
|
||||
$this->defaultValues = $this->defaults();
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga las opciones necesarias para los campos del formulario.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function loadOptions(): void
|
||||
{
|
||||
foreach ($this->options() as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga los datos de un modelo específico en el formulario para su edición.
|
||||
*
|
||||
* @param int $id ID del registro a editar.
|
||||
* @return void
|
||||
*/
|
||||
public function loadFormModel(int $id): void
|
||||
{
|
||||
if ($this->loadData($id)) {
|
||||
$this->mode = 'edit';
|
||||
|
||||
$this->dispatch($this->getDispatche('refresh-offcanvas'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga el modelo para confirmar su eliminación.
|
||||
*
|
||||
* @param int $id ID del registro a eliminar.
|
||||
* @return void
|
||||
*/
|
||||
public function loadFormModelForDeletion(int $id): void
|
||||
{
|
||||
if ($this->loadData($id)) {
|
||||
$this->mode = 'delete';
|
||||
$this->confirmDeletion = false;
|
||||
|
||||
$this->dispatch($this->getDispatche('refresh-offcanvas'));
|
||||
}
|
||||
}
|
||||
|
||||
private function getDispatche(string $name): string
|
||||
{
|
||||
$model = new ($this->model());
|
||||
|
||||
$dispatches = [
|
||||
'refresh-offcanvas' => 'refresh-' . Str::kebab($model->tagName) . '-offcanvas',
|
||||
'reload-table' => 'reload-bt-' . Str::kebab($model->tagName) . 's',
|
||||
];
|
||||
|
||||
return $dispatches[$name] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Carga los datos del modelo según el ID proporcionado.
|
||||
*
|
||||
* @param int $id ID del modelo.
|
||||
* @return bool True si los datos fueron cargados correctamente.
|
||||
*/
|
||||
protected function loadData(int $id): bool
|
||||
{
|
||||
$model = $this->model()::find($id);
|
||||
|
||||
if ($model) {
|
||||
$data = $model->only(['id', ...$this->fields()]);
|
||||
|
||||
$this->applyCasts($data);
|
||||
$this->fill($data);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ===================== OPERACIONES CRUD =====================
|
||||
|
||||
/**
|
||||
* Método principal para enviar el formulario.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function onSubmit(): void
|
||||
{
|
||||
$this->successProcess = false;
|
||||
$this->validationError = false;
|
||||
|
||||
if(!$this->mode)
|
||||
$this->mode = 'create';
|
||||
|
||||
DB::beginTransaction(); // Iniciar transacción
|
||||
|
||||
try {
|
||||
if($this->mode === 'delete'){
|
||||
$this->delete();
|
||||
|
||||
}else{
|
||||
$this->save();
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
DB::rollBack();
|
||||
$this->handleValidationException($e);
|
||||
|
||||
} catch (QueryException $e) {
|
||||
DB::rollBack();
|
||||
$this->handleDatabaseException($e);
|
||||
|
||||
} catch (ModelNotFoundException $e) {
|
||||
DB::rollBack();
|
||||
$this->handleException('danger', 'Registro no encontrado.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
DB::rollBack(); // Revertir la transacción si ocurre un error
|
||||
$this->handleException('danger', 'Error al eliminar el registro: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Guarda o actualiza un registro en la base de datos.
|
||||
*
|
||||
* @return void
|
||||
* @throws ValidationException
|
||||
*/
|
||||
protected function save(): void
|
||||
{
|
||||
// Valida incluyendo atributos personalizados
|
||||
$validatedData = $this->validate(
|
||||
$this->dynamicRules($this->mode),
|
||||
$this->messages(),
|
||||
$this->attributes()
|
||||
);
|
||||
|
||||
$this->convertEmptyValuesToNull($validatedData);
|
||||
$this->applyCasts($validatedData);
|
||||
|
||||
$this->beforeSave($validatedData);
|
||||
$record = $this->model()::updateOrCreate(['id' => $this->id], $validatedData);
|
||||
$this->afterSave($record);
|
||||
|
||||
$this->handleSuccess('success', ucfirst($this->singularName) . " guardado correctamente.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Elimina un registro en la base de datos.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function delete(): void
|
||||
{
|
||||
$this->validate($this->dynamicRules(
|
||||
'delete',
|
||||
$this->messages(),
|
||||
$this->attributes()
|
||||
));
|
||||
|
||||
$record = $this->model()::findOrFail($this->id);
|
||||
|
||||
$this->beforeDelete($record);
|
||||
$record->delete();
|
||||
$this->afterDelete($record);
|
||||
|
||||
$this->handleSuccess('warning', ucfirst($this->singularName) . " eliminado.");
|
||||
}
|
||||
|
||||
// ===================== HOOKS DE ACCIONES CRUD =====================
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta antes de guardar datos en la base de datos.
|
||||
*
|
||||
* Este método permite realizar modificaciones o preparar los datos antes de ser validados
|
||||
* y almacenados. Es útil para formatear datos, agregar valores calculados o realizar
|
||||
* operaciones previas a la persistencia.
|
||||
*
|
||||
* @param array $data Datos validados que se almacenarán. Se pasan por referencia,
|
||||
* por lo que cualquier cambio aquí afectará directamente los datos guardados.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function beforeSave(array &$data): void {}
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta después de guardar o actualizar un registro en la base de datos.
|
||||
*
|
||||
* Ideal para ejecutar tareas posteriores al guardado, como enviar notificaciones,
|
||||
* registrar auditorías o realizar acciones en otros modelos relacionados.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $record El modelo que fue guardado, conteniendo
|
||||
* los datos actualizados.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function afterSave($record): void {}
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta antes de eliminar un registro de la base de datos.
|
||||
*
|
||||
* Permite validar si el registro puede ser eliminado o realizar tareas previas
|
||||
* como desasociar relaciones, eliminar archivos relacionados o verificar restricciones.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $record El modelo que está por ser eliminado.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function beforeDelete($record): void {}
|
||||
|
||||
/**
|
||||
* Hook que se ejecuta después de eliminar un registro de la base de datos.
|
||||
*
|
||||
* Útil para realizar acciones adicionales tras la eliminación, como limpiar datos relacionados,
|
||||
* eliminar archivos vinculados o registrar eventos de auditoría.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $record El modelo eliminado. Aunque ya no existe en la base de datos,
|
||||
* se conserva la información del registro en memoria.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function afterDelete($record): void {}
|
||||
|
||||
// ===================== MANEJO DE VALIDACIONES Y EXCEPCIONES =====================
|
||||
|
||||
/**
|
||||
* Maneja las excepciones de validación.
|
||||
*
|
||||
* Este método captura los errores de validación, los agrega al error bag de Livewire
|
||||
* y dispara un evento para manejar el fallo de validación, útil en formularios modales.
|
||||
*
|
||||
* @param ValidationException $e Excepción de validación capturada.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleValidationException(ValidationException $e): void
|
||||
{
|
||||
$this->setErrorBag($e->validator->errors());
|
||||
|
||||
// Notifica al usuario que ocurrió un error de validación
|
||||
$this->handleException('danger', 'Error en la validación de los datos.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja las excepciones relacionadas con la base de datos.
|
||||
*
|
||||
* Analiza el código de error de la base de datos y genera un mensaje de error específico
|
||||
* para la situación. También se encarga de enviar una notificación de error.
|
||||
*
|
||||
* @param QueryException $e Excepción capturada durante la ejecución de una consulta.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleDatabaseException(QueryException $e): void
|
||||
{
|
||||
$errorMessage = match ($e->errorInfo[1]) {
|
||||
1452 => "Una clave foránea no es válida.",
|
||||
1062 => $this->extractDuplicateField($e->getMessage()),
|
||||
1451 => "No se puede eliminar el registro porque está en uso.",
|
||||
default => env('APP_DEBUG') ? $e->getMessage() : "Error inesperado en la base de datos.",
|
||||
};
|
||||
|
||||
$this->handleException('danger', $errorMessage, 'form', 120000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maneja cualquier tipo de excepción general y envía una notificación al usuario.
|
||||
*
|
||||
* @param string $type El tipo de notificación (success, danger, warning).
|
||||
* @param string $message El mensaje que se mostrará al usuario.
|
||||
* @param string $target El contenedor donde se mostrará la notificación (por defecto 'form').
|
||||
* @param int $delay Tiempo en milisegundos que durará la notificación en pantalla.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleException($type, $message, $target = 'form', $delay = 9000): void
|
||||
{
|
||||
$this->validationError = true;
|
||||
|
||||
$this->dispatch($this->getDispatche('refresh-offcanvas'));
|
||||
$this->dispatchNotification($type, $message, $target, $delay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrae el nombre del campo duplicado de un error de base de datos MySQL.
|
||||
*
|
||||
* Esta función se utiliza para identificar el campo específico que causó un error
|
||||
* de duplicación de clave única, y genera un mensaje personalizado para el usuario.
|
||||
*
|
||||
* @param string $errorMessage El mensaje de error completo proporcionado por MySQL.
|
||||
* @return string Mensaje de error amigable para el usuario.
|
||||
*/
|
||||
private function extractDuplicateField($errorMessage): string
|
||||
{
|
||||
preg_match("/for key 'unique_(.*?)'/", $errorMessage, $matches);
|
||||
|
||||
return isset($matches[1])
|
||||
? "El valor ingresado para '" . str_replace('_', ' ', $matches[1]) . "' ya está en uso."
|
||||
: "Ya existe un registro con este valor.";
|
||||
}
|
||||
|
||||
// ===================== NOTIFICACIONES Y ÉXITO =====================
|
||||
|
||||
/**
|
||||
* Despacha una notificación tras el éxito de una operación.
|
||||
*
|
||||
* @param string $type Tipo de notificación (success, warning, danger)
|
||||
* @param string $message Mensaje a mostrar.
|
||||
* @return void
|
||||
*/
|
||||
protected function handleSuccess(string $type, string $message): void
|
||||
{
|
||||
$this->successProcess = true;
|
||||
|
||||
$this->dispatch($this->getDispatche('refresh-offcanvas'));
|
||||
$this->dispatch($this->getDispatche('reload-table'));
|
||||
|
||||
$this->dispatchNotification($type, $message, 'index');
|
||||
}
|
||||
|
||||
/**
|
||||
* Envía una notificación al navegador.
|
||||
*
|
||||
* @param string $type Tipo de notificación (success, danger, etc.)
|
||||
* @param string $message Mensaje de la notificación
|
||||
* @param string $target Destino (form, index)
|
||||
* @param int $delay Duración de la notificación en milisegundos
|
||||
*/
|
||||
protected function dispatchNotification($type, $message, $target = 'form', $delay = 9000): void
|
||||
{
|
||||
$model = new ($this->model());
|
||||
|
||||
$this->tagName = $model->tagName;
|
||||
$this->columnNameLabel = $model->columnNameLabel;
|
||||
$this->singularName = $model->singularName;
|
||||
|
||||
$tagOffcanvas = ucfirst(Str::camel($model->tagName));
|
||||
|
||||
$targetNotifies = [
|
||||
"index" => '#bt-' . Str::kebab($model->tagName) . 's .notification-container',
|
||||
"form" => "#offcanvas{$tagOffcanvas} .notification-container",
|
||||
];
|
||||
|
||||
$this->dispatch(
|
||||
'notification',
|
||||
target: $target === 'index' ? $targetNotifies['index'] : $targetNotifies['form'],
|
||||
type: $type,
|
||||
message: $message,
|
||||
delay: $delay
|
||||
);
|
||||
}
|
||||
|
||||
// ===================== FORMULARIO Y CONVERSIÓN DE DATOS =====================
|
||||
|
||||
/**
|
||||
* Convierte los valores vacíos a `null` en los campos que son configurados como `nullable`.
|
||||
*
|
||||
* Esta función verifica las reglas de validación actuales y transforma todos los campos vacíos
|
||||
* en valores `null` si las reglas permiten valores nulos. Es útil para evitar insertar cadenas vacías
|
||||
* en la base de datos donde se espera un valor nulo.
|
||||
*
|
||||
* @param array $data Los datos del formulario que se deben procesar.
|
||||
* @return void
|
||||
*/
|
||||
protected function convertEmptyValuesToNull(array &$data): void
|
||||
{
|
||||
$nullableFields = array_keys(array_filter($this->dynamicRules($this->mode), function ($rules) {
|
||||
return in_array('nullable', (array) $rules);
|
||||
}));
|
||||
|
||||
foreach ($nullableFields as $field) {
|
||||
if (isset($data[$field]) && $data[$field] === '') {
|
||||
$data[$field] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aplica tipos de datos definidos en `$casts` a los campos del formulario.
|
||||
*
|
||||
* Esta función toma los datos de entrada y los transforma en el tipo de datos esperado según
|
||||
* lo definido en la propiedad `$casts`. Es útil para asegurar que los datos se almacenen en
|
||||
* el formato correcto, como convertir cadenas a números enteros o booleanos.
|
||||
*
|
||||
* @param array $data Los datos del formulario que necesitan ser casteados.
|
||||
* @return void
|
||||
*/
|
||||
protected function applyCasts(array &$data): void
|
||||
{
|
||||
foreach ($this->casts as $field => $type) {
|
||||
if (array_key_exists($field, $data)) {
|
||||
$data[$field] = $this->castValue($type, $data[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Castea un valor a su tipo de dato correspondiente.
|
||||
*
|
||||
* Convierte un valor dado al tipo especificado, manejando adecuadamente los valores vacíos
|
||||
* o nulos. También asegura que valores como `0` o `''` sean tratados correctamente
|
||||
* para evitar errores al almacenarlos en la base de datos.
|
||||
*
|
||||
* @param string $type El tipo de dato al que se debe convertir (`boolean`, `integer`, `float`, `string`, `array`).
|
||||
* @param mixed $value El valor que se debe castear.
|
||||
* @return mixed El valor convertido al tipo especificado.
|
||||
*/
|
||||
protected function castValue($type, $value): mixed
|
||||
{
|
||||
// Convertir valores vacíos o cero a null si corresponde
|
||||
if (is_null($value) || $value === '' || $value === '0' || $value === 0.0) {
|
||||
return match ($type) {
|
||||
'boolean' => false, // No permitir null en booleanos
|
||||
'integer' => 0, // Valor por defecto para enteros
|
||||
'float', 'double' => 0.0, // Valor por defecto para decimales
|
||||
'string' => "", // Convertir cadena vacía en null
|
||||
'array' => [], // Evitar null en arrays
|
||||
default => null, // Valor por defecto para otros tipos
|
||||
};
|
||||
}
|
||||
|
||||
// Castear el valor si no es null ni vacío
|
||||
return match ($type) {
|
||||
'boolean' => (bool) $value,
|
||||
'integer' => (int) $value,
|
||||
'float', 'double' => (float) $value,
|
||||
'string' => (string) $value,
|
||||
'array' => (array) $value,
|
||||
default => $value,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// ===================== RENDERIZACIÓN DE VISTA =====================
|
||||
|
||||
/**
|
||||
* Renderiza la vista del formulario.
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view($this->viewPath());
|
||||
}
|
||||
}
|
28
Livewire/Permissions/PermissionIndex.php
Normal file
28
Livewire/Permissions/PermissionIndex.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?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');
|
||||
}
|
||||
}
|
35
Livewire/Permissions/Permissions.php
Normal file
35
Livewire/Permissions/Permissions.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?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()
|
||||
]);
|
||||
}
|
||||
}
|
182
Livewire/Roles/RoleCards.php
Normal file
182
Livewire/Roles/RoleCards.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Roles;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class RoleCards extends Component
|
||||
{
|
||||
public $roles = [];
|
||||
public $permissions = [];
|
||||
public $roleId;
|
||||
public $name;
|
||||
public $style;
|
||||
public $title;
|
||||
public $btn_submit_text;
|
||||
public $permissionsInputs = [];
|
||||
public $destroyRoleId;
|
||||
|
||||
protected $listeners = ['saveRole', 'deleteRole'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->loadRolesAndPermissions();
|
||||
$this->dispatch('reloadForm');
|
||||
}
|
||||
|
||||
private function loadRolesAndPermissions()
|
||||
{
|
||||
$this->roles = Auth::user()->hasRole('SuperAdmin') ?
|
||||
Role::all() :
|
||||
Role::where('name', '!=', 'SuperAdmin')->get();
|
||||
|
||||
// Obtener todos los permisos
|
||||
$permissions = Permission::all()->map(function ($permission) {
|
||||
$name = $permission->name;
|
||||
$action = substr($name, strrpos($name, '.') + 1);
|
||||
|
||||
return [
|
||||
'group_name' => $permission->group_name,
|
||||
'sub_group_name' => $permission->sub_group_name,
|
||||
$action => $name // Agregar la acción directamente al array
|
||||
];
|
||||
})->groupBy('group_name'); // Agrupar los permisos por grupo
|
||||
|
||||
|
||||
// Procesar los permisos agrupados para cargarlos en el componente
|
||||
$permissionsInputs = [];
|
||||
|
||||
$this->permissions = $permissions->map(function ($groupPermissions) use (&$permissionsInputs) {
|
||||
$permission = [
|
||||
'group_name' => $groupPermissions[0]['group_name'], // Tomar el grupo del primer permiso del grupo
|
||||
'sub_group_name' => $groupPermissions[0]['sub_group_name'], // Tomar la descripción del primer permiso del grupo
|
||||
];
|
||||
|
||||
// Agregar todas las acciones al permissionsInputs y al permission
|
||||
foreach ($groupPermissions as $permissionData) {
|
||||
foreach ($permissionData as $key => $value) {
|
||||
if ($key !== 'sub_group_name' && $key !== 'group_name') {
|
||||
$permissionsInputs[str_replace('.', '_', $value)] = false;
|
||||
$permission[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $permission;
|
||||
});
|
||||
|
||||
$this->permissionsInputs = $permissionsInputs;
|
||||
}
|
||||
|
||||
public function loadRoleData($action, $roleId = false)
|
||||
{
|
||||
$this->resetForm();
|
||||
|
||||
$this->title = 'Agregar un nuevo rol';
|
||||
$this->btn_submit_text = 'Crear nuevo rol';
|
||||
|
||||
if ($roleId) {
|
||||
$role = Role::findOrFail($roleId);
|
||||
|
||||
switch ($action) {
|
||||
case 'view':
|
||||
$this->title = $role->name;
|
||||
$this->name = $role->name;
|
||||
$this->style = $role->style;
|
||||
$this->dispatch('deshabilitarFormulario');
|
||||
break;
|
||||
|
||||
case 'update':
|
||||
$this->title = 'Editar rol';
|
||||
$this->btn_submit_text = 'Guardar cambios';
|
||||
$this->roleId = $roleId;
|
||||
$this->name = $role->name;
|
||||
$this->style = $role->style;
|
||||
$this->dispatch('habilitarFormulario');
|
||||
break;
|
||||
|
||||
case 'clone':
|
||||
$this->style = $role->style;
|
||||
$this->dispatch('habilitarFormulario');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($role->permissions as $permission) {
|
||||
$this->permissionsInputs[str_replace('.', '_', $permission->name)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->dispatch('reloadForm');
|
||||
}
|
||||
|
||||
public function loadDestroyRoleData() {}
|
||||
|
||||
public function saveRole()
|
||||
{
|
||||
$permissions = [];
|
||||
|
||||
foreach ($this->permissionsInputs as $permission => $value) {
|
||||
if ($value === true)
|
||||
$permissions[] = str_replace('_', '.', $permission);
|
||||
}
|
||||
|
||||
if ($this->roleId) {
|
||||
$role = Role::find($this->roleId);
|
||||
|
||||
$role->name = $this->name;
|
||||
$role->style = $this->style;
|
||||
|
||||
$role->save();
|
||||
|
||||
$role->syncPermissions($permissions);
|
||||
} else {
|
||||
$role = Role::create([
|
||||
'name' => $this->name,
|
||||
'style' => $this->style,
|
||||
]);
|
||||
|
||||
$role->syncPermissions($permissions);
|
||||
}
|
||||
|
||||
$this->loadRolesAndPermissions();
|
||||
|
||||
$this->dispatch('modalHide');
|
||||
$this->dispatch('reloadForm');
|
||||
}
|
||||
|
||||
public function deleteRole()
|
||||
{
|
||||
$role = Role::find($this->destroyRoleId);
|
||||
|
||||
if ($role)
|
||||
$role->delete();
|
||||
|
||||
$this->loadRolesAndPermissions();
|
||||
|
||||
$this->dispatch('modalDeleteHide');
|
||||
$this->dispatch('reloadForm');
|
||||
}
|
||||
|
||||
private function resetForm()
|
||||
{
|
||||
$this->roleId = '';
|
||||
$this->name = '';
|
||||
$this->style = '';
|
||||
|
||||
foreach ($this->permissionsInputs as $key => $permission) {
|
||||
$this->permissionsInputs[$key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.roles.cards');
|
||||
}
|
||||
}
|
61
Livewire/Roles/RoleIndex.php
Normal file
61
Livewire/Roles/RoleIndex.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Roles;
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
use Spatie\Permission\Models\Role;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
|
||||
class RoleIndex extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public $roleName;
|
||||
public $selectedRole;
|
||||
public $permissions = [];
|
||||
public $availablePermissions;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->availablePermissions = Permission::all();
|
||||
}
|
||||
|
||||
public function createRole()
|
||||
{
|
||||
$this->validate([
|
||||
'roleName' => 'required|unique:roles,name'
|
||||
]);
|
||||
|
||||
$role = Role::create(['name' => $this->roleName]);
|
||||
$this->reset(['roleName']);
|
||||
session()->flash('message', 'Rol creado con éxito.');
|
||||
}
|
||||
|
||||
public function selectRole($roleId)
|
||||
{
|
||||
$this->selectedRole = Role::find($roleId);
|
||||
$this->permissions = $this->selectedRole->permissions->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
public function updateRolePermissions()
|
||||
{
|
||||
if ($this->selectedRole) {
|
||||
$this->selectedRole->syncPermissions($this->permissions);
|
||||
session()->flash('message', 'Permisos actualizados correctamente.');
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteRole($roleId)
|
||||
{
|
||||
Role::find($roleId)->delete();
|
||||
session()->flash('message', 'Rol eliminado.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.roles', [
|
||||
'index' => Role::paginate(10)
|
||||
]);
|
||||
}
|
||||
}
|
174
Livewire/Table/AbstractIndexComponent.php
Normal file
174
Livewire/Table/AbstractIndexComponent.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Table;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
|
||||
/**
|
||||
* Clase base abstracta para la creación de componentes tipo "Index" con Livewire.
|
||||
*
|
||||
* Provee una estructura general para:
|
||||
* - Configurar y renderizar tablas con Bootstrap Table.
|
||||
* - Definir columnas y formatos de manera estándar.
|
||||
* - Manejar búsquedas, filtros, o catálogos necesarios.
|
||||
* - Centralizar la lógica de montaje (mount).
|
||||
*
|
||||
* @package Koneko\VuexyAdmin\Livewire\Table
|
||||
*/
|
||||
abstract class AbstractIndexComponent extends Component
|
||||
{
|
||||
/**
|
||||
* Configuración principal para la tabla con Bootstrap Table.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $bt_datatable = [];
|
||||
|
||||
/**
|
||||
* Tag identificador del componente, derivado del modelo.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $tagName;
|
||||
|
||||
/**
|
||||
* Nombre singular del modelo (para mensajes, etiquetado, etc.).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $singularName;
|
||||
|
||||
/**
|
||||
* Identificador único del formulario (vinculado al Offcanvas o Modal).
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $formId;
|
||||
|
||||
/**
|
||||
* Método para obtener la instancia del modelo asociado.
|
||||
*
|
||||
* Debe retornarse una instancia (o la clase) del modelo Eloquent que maneja este Index.
|
||||
*
|
||||
* @return Model|string
|
||||
*/
|
||||
abstract protected function model(): string;
|
||||
|
||||
/**
|
||||
* Define las columnas (header) de la tabla. Este array se fusionará
|
||||
* o se inyectará en la configuración principal $bt_datatable.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function columns(): array;
|
||||
|
||||
/**
|
||||
* Define el formato (formatter) de las columnas.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function format(): array;
|
||||
|
||||
/**
|
||||
* Retorna la ruta de la vista Blade que renderizará el componente.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function viewPath(): string;
|
||||
|
||||
/**
|
||||
* Método que define la configuración base del DataTable.
|
||||
* Aquí puedes poner ajustes comunes (exportFileName, paginación, etc.).
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function bootstraptableConfig(): array
|
||||
{
|
||||
return [
|
||||
'sortName' => 'id', // Campo por defecto para ordenar
|
||||
'exportFileName' => 'Listado', // Nombre de archivo para exportar
|
||||
'showFullscreen' => false,
|
||||
'showPaginationSwitch'=> false,
|
||||
'showRefresh' => false,
|
||||
'pagination' => false,
|
||||
// Agrega aquí cualquier otra configuración por defecto que uses
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Se ejecuta al montar el componente Livewire.
|
||||
* Configura $tagName, $singularName, $formId y $bt_datatable.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
// Obtenemos el modelo
|
||||
$model = $this->model();
|
||||
if (is_string($model)) {
|
||||
// Si se retornó la clase en abstract protected function model(),
|
||||
// instanciamos manualmente
|
||||
$model = new $model;
|
||||
}
|
||||
|
||||
// Usamos las propiedades definidas en el modelo
|
||||
// (tagName, singularName, etc.), si existen en el modelo.
|
||||
// Ajusta nombres según tu convención.
|
||||
$this->tagName = $model->tagName ?? Str::snake(class_basename($model));
|
||||
$this->singularName = $model->singularName ?? class_basename($model);
|
||||
$this->formId = Str::kebab($this->tagName) . '-form';
|
||||
|
||||
// Inicia la configuración principal de la tabla
|
||||
$this->setupDataTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Combina la configuración base de la tabla con las columnas y formatos
|
||||
* definidos en las clases hijas.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function setupDataTable(): void
|
||||
{
|
||||
$baseConfig = $this->bootstraptableConfig();
|
||||
|
||||
$this->bt_datatable = array_merge($baseConfig, [
|
||||
'header' => $this->columns(),
|
||||
'format' => $this->format(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renderiza la vista definida en viewPath().
|
||||
*
|
||||
* @return \Illuminate\View\View
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view($this->viewPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ejemplo de método para la lógica de filtrado que podrías sobreescribir en la clase hija.
|
||||
*
|
||||
* @param array $criteria
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
protected function applyFilters($criteria = [])
|
||||
{
|
||||
// Aplica tu lógica de filtros, búsquedas, etc.
|
||||
// La clase hija podría sobrescribir este método o llamarlo desde su propia lógica.
|
||||
$query = $this->model()::query();
|
||||
|
||||
// Por ejemplo:
|
||||
/*
|
||||
if (!empty($criteria['store_id'])) {
|
||||
$query->where('store_id', $criteria['store_id']);
|
||||
}
|
||||
*/
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
31
Livewire/Users/UserCount.php
Normal file
31
Livewire/Users/UserCount.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
|
||||
use Livewire\Component;
|
||||
|
||||
class UserCount extends Component
|
||||
{
|
||||
public $total, $enabled, $disabled;
|
||||
|
||||
protected $listeners = ['refreshUserCount' => 'updateCounts'];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->updateCounts();
|
||||
}
|
||||
|
||||
public function updateCounts()
|
||||
{
|
||||
$this->total = User::count();
|
||||
$this->enabled = User::where('status', User::STATUS_ENABLED)->count();
|
||||
$this->disabled = User::where('status', User::STATUS_DISABLED)->count();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('vuexy-admin::livewire.users.count');
|
||||
}
|
||||
}
|
306
Livewire/Users/UserForm.php
Normal file
306
Livewire/Users/UserForm.php
Normal file
@ -0,0 +1,306 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use Illuminate\Validation\Rule;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormComponent;
|
||||
use Koneko\SatCatalogs\Models\{Colonia, Estado, Localidad, Municipio, Pais, RegimenFiscal};
|
||||
use Koneko\VuexyAdmin\Models\Store;
|
||||
|
||||
/**
|
||||
* Class UserForm
|
||||
*
|
||||
* Componente Livewire para manejar el formulario CRUD de sucursales en el sistema ERP.
|
||||
* Implementa la creación, edición y eliminación de sucursales con validaciones dinámicas.
|
||||
*/
|
||||
class UserForm extends AbstractFormComponent
|
||||
{
|
||||
/**
|
||||
* Campos específicos del formulario.
|
||||
*/
|
||||
public $code, $name, $description, $manager_id, $rfc, $nombre_fiscal, $c_regimen_fiscal,
|
||||
$domicilio_fiscal, $serie_ingresos, $serie_egresos, $serie_pagos, $c_codigo_postal,
|
||||
$c_pais, $c_estado, $c_localidad, $c_municipio, $c_colonia, $direccion, $num_ext,
|
||||
$num_int, $email, $tel, $tel2, $lat, $lng, $show_on_website, $enable_ecommerce, $status;
|
||||
|
||||
|
||||
public $confirmDeletion = false;
|
||||
|
||||
/**
|
||||
* Listas de opciones para selects en el formulario.
|
||||
*/
|
||||
public $manager_id_options = [],
|
||||
$c_regimen_fiscal_options = [],
|
||||
$c_pais_options = [],
|
||||
$c_estado_options = [],
|
||||
$c_localidad_options = [],
|
||||
$c_municipio_options = [],
|
||||
$c_colonia_options = [];
|
||||
|
||||
/**
|
||||
* Montar el formulario e inicializar datos específicos.
|
||||
*
|
||||
* @param string $mode Modo del formulario: create, edit, delete.
|
||||
* @param Store|null $store El modelo Store si está en modo edición o eliminación.
|
||||
*/
|
||||
public function mount(string $mode = 'create', mixed $store = null): void
|
||||
{
|
||||
parent::mount($mode, $store->id ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cargar opciones de formularios según el modo actual.
|
||||
*
|
||||
* @param string $mode
|
||||
*/
|
||||
private function loadOptions(string $mode): void
|
||||
{
|
||||
$this->manager_id_options = User::getUsersListWithInactive($this->manager_id, ['type' => 'user', 'status' => 1]);
|
||||
$this->c_regimen_fiscal_options = RegimenFiscal::selectList();
|
||||
$this->c_pais_options = Pais::selectList();
|
||||
$this->c_estado_options = Estado::selectList($this->c_pais)->toArray();
|
||||
|
||||
if ($mode !== 'create') {
|
||||
$this->c_localidad_options = Localidad::selectList($this->c_estado)->toArray();
|
||||
$this->c_municipio_options = Municipio::selectList($this->c_estado, $this->c_municipio)->toArray();
|
||||
$this->c_colonia_options = Colonia::selectList($this->c_codigo_postal, $this->c_colonia)->toArray();
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== MÉTODOS OBLIGATORIOS =====================
|
||||
|
||||
/**
|
||||
* Devuelve el modelo Eloquent asociado.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function model(): string
|
||||
{
|
||||
return Store::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reglas de validación dinámicas según el modo actual.
|
||||
*
|
||||
* @param string $mode
|
||||
* @return array
|
||||
*/
|
||||
protected function dynamicRules(string $mode): array
|
||||
{
|
||||
switch ($mode) {
|
||||
case 'create':
|
||||
case 'edit':
|
||||
return [
|
||||
'code' => [
|
||||
'required', 'string', 'alpha_num', 'max:16',
|
||||
Rule::unique('stores', 'code')->ignore($this->id)
|
||||
],
|
||||
'name' => 'required|string|max:96',
|
||||
'description' => 'nullable|string|max:1024',
|
||||
'manager_id' => 'nullable|exists:users,id',
|
||||
|
||||
// Información fiscal
|
||||
'rfc' => ['nullable', 'string', 'regex:/^([A-ZÑ&]{3,4})(\d{6})([A-Z\d]{3})$/i', 'max:13'],
|
||||
'nombre_fiscal' => 'nullable|string|max:255',
|
||||
'c_regimen_fiscal' => 'nullable|exists:sat_regimen_fiscal,c_regimen_fiscal',
|
||||
'domicilio_fiscal' => 'nullable|exists:sat_codigo_postal,c_codigo_postal',
|
||||
|
||||
// Ubicación
|
||||
'c_pais' => 'nullable|exists:sat_pais,c_pais|string|size:3',
|
||||
'c_estado' => 'nullable|exists:sat_estado,c_estado|string|min:2|max:3',
|
||||
'c_municipio' => 'nullable|exists:sat_municipio,c_municipio|integer',
|
||||
'c_localidad' => 'nullable|integer',
|
||||
'c_codigo_postal' => 'nullable|exists:sat_codigo_postal,c_codigo_postal|integer',
|
||||
'c_colonia' => 'nullable|exists:sat_colonia,c_colonia|integer',
|
||||
'direccion' => 'nullable|string|max:255',
|
||||
'num_ext' => 'nullable|string|max:50',
|
||||
'num_int' => 'nullable|string|max:50',
|
||||
'lat' => 'nullable|numeric|between:-90,90',
|
||||
'lng' => 'nullable|numeric|between:-180,180',
|
||||
|
||||
// Contacto
|
||||
'email' => ['nullable', 'email', 'required_if:enable_ecommerce,true'],
|
||||
'tel' => ['nullable', 'regex:/^[0-9\s\-\+\(\)]+$/', 'max:15'],
|
||||
'tel2' => ['nullable', 'regex:/^[0-9\s\-\+\(\)]+$/', 'max:15'],
|
||||
|
||||
// Configuración web y estado
|
||||
'show_on_website' => 'nullable|boolean',
|
||||
'enable_ecommerce' => 'nullable|boolean',
|
||||
'status' => 'nullable|boolean',
|
||||
];
|
||||
|
||||
case 'delete':
|
||||
return [
|
||||
'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación
|
||||
];
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inicializa los datos del formulario en función del modo.
|
||||
*
|
||||
* @param Store|null $store
|
||||
* @param string $mode
|
||||
*/
|
||||
protected function initializeFormData(mixed $store, string $mode): void
|
||||
{
|
||||
if ($store) {
|
||||
$this->code = $store->code;
|
||||
$this->name = $store->name;
|
||||
$this->description = $store->description;
|
||||
$this->manager_id = $store->manager_id;
|
||||
$this->rfc = $store->rfc;
|
||||
$this->nombre_fiscal = $store->nombre_fiscal;
|
||||
$this->c_regimen_fiscal = $store->c_regimen_fiscal;
|
||||
$this->domicilio_fiscal = $store->domicilio_fiscal;
|
||||
$this->c_pais = $store->c_pais;
|
||||
$this->c_estado = $store->c_estado;
|
||||
$this->c_municipio = $store->c_municipio;
|
||||
$this->c_localidad = $store->c_localidad;
|
||||
$this->c_codigo_postal = $store->c_codigo_postal;
|
||||
$this->c_colonia = $store->c_colonia;
|
||||
$this->direccion = $store->direccion;
|
||||
$this->num_ext = $store->num_ext;
|
||||
$this->num_int = $store->num_int;
|
||||
$this->lat = $store->lat;
|
||||
$this->lng = $store->lng;
|
||||
$this->email = $store->email;
|
||||
$this->tel = $store->tel;
|
||||
$this->tel2 = $store->tel2;
|
||||
$this->show_on_website = (bool) $store->show_on_website;
|
||||
$this->enable_ecommerce = (bool) $store->enable_ecommerce;
|
||||
$this->status = (bool) $store->status;
|
||||
|
||||
} else {
|
||||
$this->c_pais = 'MEX';
|
||||
$this->status = true;
|
||||
$this->show_on_website = false;
|
||||
$this->enable_ecommerce = false;
|
||||
}
|
||||
|
||||
$this->loadOptions($mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepara los datos validados para su almacenamiento.
|
||||
*
|
||||
* @param array $validatedData
|
||||
* @return array
|
||||
*/
|
||||
protected function prepareData(array $validatedData): array
|
||||
{
|
||||
return [
|
||||
'code' => $validatedData['code'],
|
||||
'name' => $validatedData['name'],
|
||||
'description' => strip_tags($validatedData['description']),
|
||||
'manager_id' => $validatedData['manager_id'],
|
||||
'rfc' => $validatedData['rfc'],
|
||||
'nombre_fiscal' => $validatedData['nombre_fiscal'],
|
||||
'c_regimen_fiscal' => $validatedData['c_regimen_fiscal'],
|
||||
'domicilio_fiscal' => $validatedData['domicilio_fiscal'],
|
||||
'c_codigo_postal' => $validatedData['c_codigo_postal'],
|
||||
'c_pais' => $validatedData['c_pais'],
|
||||
'c_estado' => $validatedData['c_estado'],
|
||||
'c_localidad' => $validatedData['c_localidad'],
|
||||
'c_municipio' => $validatedData['c_municipio'],
|
||||
'c_colonia' => $validatedData['c_colonia'],
|
||||
'direccion' => $validatedData['direccion'],
|
||||
'num_ext' => $validatedData['num_ext'],
|
||||
'num_int' => $validatedData['num_int'],
|
||||
'email' => $validatedData['email'],
|
||||
'tel' => $validatedData['tel'],
|
||||
'tel2' => $validatedData['tel2'],
|
||||
'lat' => $validatedData['lat'],
|
||||
'lng' => $validatedData['lng'],
|
||||
'status' => $validatedData['status'],
|
||||
'show_on_website' => $validatedData['show_on_website'],
|
||||
'enable_ecommerce' => $validatedData['enable_ecommerce'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Definición de los contenedores de notificación.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function targetNotifies(): array
|
||||
{
|
||||
return [
|
||||
"index" => "#bt-stores .notification-container",
|
||||
"form" => "#store-form .notification-container",
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruta de vista asociada al formulario.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View
|
||||
*/
|
||||
protected function viewPath(): string
|
||||
{
|
||||
return 'vuexy-store-manager::livewire.stores.form';
|
||||
}
|
||||
|
||||
// ===================== VALIDACIONES =====================
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'code' => 'código de sucursal',
|
||||
'name' => 'nombre de la sucursal',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'code.required' => 'El código de la sucursal es obligatorio.',
|
||||
'code.unique' => 'Este código ya está en uso por otra sucursal.',
|
||||
'name.required' => 'El nombre de la sucursal es obligatorio.',
|
||||
];
|
||||
}
|
||||
|
||||
// ===================== PREPARACIÓN DE DATOS =====================
|
||||
|
||||
// ===================== NOTIFICACIONES Y EVENTOS =====================
|
||||
|
||||
/**
|
||||
* Definición de los eventos del componente.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function dispatches(): array
|
||||
{
|
||||
return [
|
||||
'on-failed-validation' => 'on-failed-validation-store',
|
||||
'on-hydrate' => 'on-hydrate-store-modal',
|
||||
];
|
||||
}
|
||||
|
||||
// ===================== REDIRECCIÓN =====================
|
||||
|
||||
/**
|
||||
* Define la ruta de redirección tras guardar o eliminar.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getRedirectRoute(): string
|
||||
{
|
||||
return 'admin.core.user.index';
|
||||
}
|
||||
|
||||
}
|
115
Livewire/Users/UserIndex.copy.php
Normal file
115
Livewire/Users/UserIndex.copy.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?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),
|
||||
]);
|
||||
}
|
||||
}
|
299
Livewire/Users/UserIndex.php
Normal file
299
Livewire/Users/UserIndex.php
Normal file
@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use Livewire\WithFileUploads;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
|
||||
|
||||
class UserIndex extends AbstractIndexComponent
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $doc_file;
|
||||
public $dropzoneVisible = true;
|
||||
|
||||
/**
|
||||
* Almacena rutas útiles para la funcionalidad de edición o eliminación.
|
||||
*/
|
||||
public $routes = [];
|
||||
|
||||
/**
|
||||
* Método que define la clase o instancia del modelo a usar en este Index.
|
||||
*/
|
||||
protected function model(): string
|
||||
{
|
||||
return User::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna las columnas (header) de la tabla.
|
||||
*/
|
||||
protected function columns(): array
|
||||
{
|
||||
return [
|
||||
'action' => 'Acciones',
|
||||
'code' => 'Código personal',
|
||||
'full_name' => 'Nombre Completo',
|
||||
'email' => 'Correo Electrónico',
|
||||
'parent_name' => 'Responsable',
|
||||
'parent_email' => 'Correo Responsable',
|
||||
'company' => 'Empresa',
|
||||
'birth_date' => 'Fecha de Nacimiento',
|
||||
'hire_date' => 'Fecha de Contratación',
|
||||
'curp' => 'CURP',
|
||||
'nss' => 'NSS',
|
||||
'job_title' => 'Puesto',
|
||||
'rfc' => 'RFC',
|
||||
'nombre_fiscal' => 'Nombre Fiscal',
|
||||
'profile_photo_path' => 'Foto de Perfil',
|
||||
'is_partner' => 'Socio',
|
||||
'is_employee' => 'Empleado',
|
||||
'is_prospect' => 'Prospecto',
|
||||
'is_customer' => 'Cliente',
|
||||
'is_provider' => 'Proveedor',
|
||||
'is_user' => 'Usuario',
|
||||
'status' => 'Estatus',
|
||||
'creator' => 'Creado Por',
|
||||
'creator_email' => 'Correo Creador',
|
||||
'created_at' => 'Fecha de Creación',
|
||||
'updated_at' => 'Última Modificación',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna el formato (formatter) para cada columna.
|
||||
*/
|
||||
protected function format(): array
|
||||
{
|
||||
return [
|
||||
'action' => [
|
||||
'formatter' => 'userActionFormatter',
|
||||
'onlyFormatter' => true,
|
||||
],
|
||||
'code' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBadgeFormatter',
|
||||
'params' => ['color' => 'secondary'],
|
||||
],
|
||||
'align' => 'center',
|
||||
'switchable' => false,
|
||||
],
|
||||
'full_name' => [
|
||||
'formatter' => 'userProfileFormatter',
|
||||
],
|
||||
'email' => [
|
||||
'formatter' => 'emailFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'parent_name' => [
|
||||
'formatter' => 'contactParentFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'agent_name' => [
|
||||
'formatter' => 'agentFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'company' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
],
|
||||
'curp' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'nss' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'job_title' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'rfc' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'nombre_fiscal' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'domicilio_fiscal' => [
|
||||
'visible' => false,
|
||||
],
|
||||
'c_uso_cfdi' => [
|
||||
'formatter' => 'usoCfdiFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'tipo_persona' => [
|
||||
'formatter' => 'dynamicBadgeFormatter',
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
'c_regimen_fiscal' => [
|
||||
'formatter' => 'regimenFiscalFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'birth_date' => [
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
'hire_date' => [
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
'estado' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
],
|
||||
'municipio' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
],
|
||||
'localidad' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'is_partner' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'is_employee' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'is_prospect' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'is_customer' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'is_provider' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'is_user' => [
|
||||
'formatter' => [
|
||||
'name' => 'dynamicBooleanFormatter',
|
||||
'params' => ['tag' => 'checkSI'],
|
||||
],
|
||||
'align' => 'center',
|
||||
],
|
||||
'status' => [
|
||||
'formatter' => 'statusIntBadgeBgFormatter',
|
||||
'align' => 'center',
|
||||
],
|
||||
'creator' => [
|
||||
'formatter' => 'creatorFormatter',
|
||||
'visible' => false,
|
||||
],
|
||||
'created_at' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
'updated_at' => [
|
||||
'formatter' => 'textNowrapFormatter',
|
||||
'align' => 'center',
|
||||
'visible' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
parent::mount();
|
||||
|
||||
// Definimos las rutas específicas de este componente
|
||||
$this->routes = [
|
||||
'admin.user.show' => route('admin.core.users.show', ['user' => ':id']),
|
||||
'admin.user.edit' => route('admin.core.users.edit', ['user' => ':id']),
|
||||
'admin.user.delete' => route('admin.core.users.delete', ['user' => ':id']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retorna la vista a renderizar por este componente.
|
||||
*/
|
||||
protected function viewPath(): string
|
||||
{
|
||||
return 'vuexy-admin::livewire.users.index';
|
||||
}
|
||||
}
|
295
Livewire/Users/UserOffCanvasForm.php
Normal file
295
Livewire/Users/UserOffCanvasForm.php
Normal file
@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormOffCanvasComponent;
|
||||
use Koneko\VuexyAdmin\Models\User;
|
||||
use Koneko\VuexyContacts\Services\{ContactCatalogService,ConstanciaFiscalService,FacturaXmlService};
|
||||
use Koneko\VuexyStoreManager\Services\StoreCatalogService;
|
||||
use Livewire\WithFileUploads;
|
||||
|
||||
/**
|
||||
* Class UserOffCanvasForm
|
||||
*
|
||||
* Componente Livewire para gestionar almacenes.
|
||||
* Extiende la clase AbstractFormOffCanvasComponent e implementa validaciones dinámicas,
|
||||
* manejo de formularios, eventos y actualizaciones en tiempo real.
|
||||
*
|
||||
* @package Koneko\VuexyAdmin\Livewire\Users
|
||||
*/
|
||||
class UserOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $doc_file;
|
||||
public $dropzoneVisible = true;
|
||||
|
||||
|
||||
/**
|
||||
* Propiedades del formulario relacionadas con el usuario.
|
||||
*/
|
||||
public $code,
|
||||
$parent_id,
|
||||
$name,
|
||||
$last_name,
|
||||
$email,
|
||||
$company,
|
||||
$rfc,
|
||||
$nombre_fiscal,
|
||||
$tipo_persona,
|
||||
$c_regimen_fiscal,
|
||||
$domicilio_fiscal,
|
||||
$is_partner,
|
||||
$is_employee,
|
||||
$is_prospect,
|
||||
$is_customer,
|
||||
$is_provider,
|
||||
$status;
|
||||
|
||||
/**
|
||||
* Listas de opciones para selects en el formulario.
|
||||
*/
|
||||
public $store_options = [],
|
||||
$work_center_options = [],
|
||||
$manager_options = [];
|
||||
|
||||
/**
|
||||
* Eventos de escucha de Livewire.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listeners = [
|
||||
'editUsers' => 'loadFormModel',
|
||||
'confirmDeletionUsers' => 'loadFormModelForDeletion',
|
||||
];
|
||||
|
||||
/**
|
||||
* Definición de tipos de datos que se deben castear.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'status' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* Define el modelo Eloquent asociado con el formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function model(): string
|
||||
{
|
||||
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.
|
||||
*
|
||||
* @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 [
|
||||
'code' => ['required', 'string', 'max:16', Rule::unique('contact', 'code')->ignore($this->id)],
|
||||
'name' => ['required', 'string', 'max:96'],
|
||||
'notes' => ['nullable', 'string', 'max:1024'],
|
||||
'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'],
|
||||
];
|
||||
|
||||
case 'delete':
|
||||
return [
|
||||
'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación
|
||||
];
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// ===================== VALIDACIONES =====================
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function attributes(): array
|
||||
{
|
||||
return [
|
||||
'code' => 'código de usuario',
|
||||
'name' => 'nombre del usuario',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function messages(): array
|
||||
{
|
||||
return [
|
||||
'code.unique' => 'Este código ya está en uso por otro usuario.',
|
||||
'name.required' => 'El nombre del usuario es obligatorio.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga el formulario con datos del usuario y actualiza las opciones dinámicas.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function loadFormModel($id): void
|
||||
{
|
||||
parent::loadFormModel($id);
|
||||
|
||||
$this->work_center_options = $this->store_id
|
||||
? DB::table('store_work_centers')
|
||||
->where('store_id', $this->store_id)
|
||||
->pluck('name', 'id')
|
||||
->toArray()
|
||||
: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Carga el formulario para eliminar un usuario, actualizando las opciones necesarias.
|
||||
*
|
||||
* @param int $id
|
||||
*/
|
||||
public function loadFormModelForDeletion($id): void
|
||||
{
|
||||
parent::loadFormModelForDeletion($id);
|
||||
|
||||
$this->work_center_options = DB::table('store_work_centers')
|
||||
->where('store_id', $this->store_id)
|
||||
->pluck('name', 'id')
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define las opciones de los selectores desplegables.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function options(): array
|
||||
{
|
||||
$storeCatalogService = app(StoreCatalogService::class);
|
||||
$contactCatalogService = app(ContactCatalogService::class);
|
||||
|
||||
return [
|
||||
'store_options' => $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]),
|
||||
'manager_options' => $contactCatalogService->searchCatalog('users', '', ['limit' => -1]),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Procesa el documento recibido (CFDI XML o Constancia PDF).
|
||||
*/
|
||||
public function processDocument()
|
||||
{
|
||||
// Verificamos si el archivo es válido
|
||||
if (!$this->doc_file instanceof UploadedFile) {
|
||||
return $this->addError('doc_file', 'No se pudo recibir el archivo.');
|
||||
}
|
||||
|
||||
try {
|
||||
// Validar tipo de archivo
|
||||
$this->validate([
|
||||
'doc_file' => 'required|mimes:pdf,xml|max:2048'
|
||||
]);
|
||||
|
||||
|
||||
// **Detectar el tipo de documento**
|
||||
$extension = strtolower($this->doc_file->getClientOriginalExtension());
|
||||
|
||||
// **Procesar según el tipo de archivo**
|
||||
switch ($extension) {
|
||||
case 'xml':
|
||||
$service = new FacturaXmlService();
|
||||
$data = $service->processUploadedFile($this->doc_file);
|
||||
break;
|
||||
|
||||
case 'pdf':
|
||||
$service = new ConstanciaFiscalService();
|
||||
$data = $service->extractData($this->doc_file);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Formato de archivo no soportado.");
|
||||
}
|
||||
|
||||
dd($data);
|
||||
|
||||
// **Asignar los valores extraídos al formulario**
|
||||
$this->rfc = $data['rfc'] ?? null;
|
||||
$this->name = $data['name'] ?? null;
|
||||
$this->email = $data['email'] ?? null;
|
||||
$this->tel = $data['telefono'] ?? null;
|
||||
//$this->direccion = $data['domicilio_fiscal'] ?? null;
|
||||
|
||||
// Ocultar el Dropzone después de procesar
|
||||
$this->dropzoneVisible = false;
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
$this->handleValidationException($e);
|
||||
|
||||
} catch (QueryException $e) {
|
||||
$this->handleDatabaseException($e);
|
||||
|
||||
} catch (ModelNotFoundException $e) {
|
||||
$this->handleException('danger', 'Registro no encontrado.');
|
||||
|
||||
} catch (Exception $e) {
|
||||
$this->handleException('danger', 'Error al procesar el archivo: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruta de la vista asociada con este formulario.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function viewPath(): string
|
||||
{
|
||||
return 'vuexy-admin::livewire.users.offcanvas-form';
|
||||
}
|
||||
}
|
283
Livewire/Users/UserShow.php
Normal file
283
Livewire/Users/UserShow.php
Normal file
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace Koneko\VuexyAdmin\Livewire\Users;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Models\Catalog\DropdownList;
|
||||
use Koneko\SatCatalogs\Models\UsoCfdi;
|
||||
use Koneko\SatCatalogs\Models\RegimenFiscal;
|
||||
use Intervention\Image\ImageManager;
|
||||
use Livewire\WithFileUploads;
|
||||
use Livewire\Component;
|
||||
|
||||
class UserShow extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $image;
|
||||
|
||||
public User $user;
|
||||
public $status_options, $pricelists_options;
|
||||
public $regimen_fiscal_options, $uso_cfdi_options;
|
||||
public $userId,
|
||||
$name,
|
||||
$cargo,
|
||||
$profile_photo,
|
||||
$profile_photo_path,
|
||||
$email,
|
||||
$password,
|
||||
$password_confirmation,
|
||||
$tipo_persona,
|
||||
$rfc,
|
||||
$nombre_fiscal,
|
||||
$c_regimen_fiscal,
|
||||
$domicilio_fiscal,
|
||||
$c_uso_cfdi,
|
||||
$pricelist_id,
|
||||
$enable_credit,
|
||||
$credit_days,
|
||||
$credit_limit,
|
||||
$is_prospect,
|
||||
$is_customer,
|
||||
$is_provider,
|
||||
$is_user,
|
||||
$status;
|
||||
public $deleteUserImage;
|
||||
public $cuentaUsuarioAlert,
|
||||
$accesosAlert,
|
||||
$facturacionElectronicaAlert;
|
||||
|
||||
// Reglas de validación para la cuenta de usuario
|
||||
protected $rulesUser = [
|
||||
'tipo_persona' => 'nullable|integer',
|
||||
'name' => 'required|string|min:3|max:255',
|
||||
'cargo' => 'nullable|string|min:3|max:255',
|
||||
'is_prospect' => 'nullable|boolean',
|
||||
'is_customer' => 'nullable|boolean',
|
||||
'is_provider' => 'nullable|boolean',
|
||||
'is_user' => 'nullable|boolean',
|
||||
'pricelist_id' => 'nullable|integer',
|
||||
'enable_credit' => 'nullable|boolean',
|
||||
'credit_days' => 'nullable|integer',
|
||||
'credit_limit' => 'nullable|numeric|min:0|max:9999999.99|regex:/^\d{1,7}(\.\d{1,2})?$/',
|
||||
'image' => 'nullable|mimes:jpg,png|image|max:20480', // 20MB Max
|
||||
];
|
||||
|
||||
// Reglas de validación para los campos fiscales
|
||||
protected $rulesFacturacion = [
|
||||
'rfc' => 'nullable|string|max:13',
|
||||
'domicilio_fiscal' => [
|
||||
'nullable',
|
||||
'regex:/^[0-9]{5}$/',
|
||||
'exists:sat_codigo_postal,c_codigo_postal'
|
||||
],
|
||||
'nombre_fiscal' => 'nullable|string|max:255',
|
||||
'c_regimen_fiscal' => 'nullable|integer',
|
||||
'c_uso_cfdi' => 'nullable|string',
|
||||
];
|
||||
|
||||
public function mount($userId)
|
||||
{
|
||||
$this->user = User::findOrFail($userId);
|
||||
|
||||
$this->reloadUserData();
|
||||
|
||||
$this->pricelists_options = DropdownList::selectList(DropdownList::POS_PRICELIST);
|
||||
|
||||
$this->status_options = [
|
||||
User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED],
|
||||
User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED],
|
||||
];
|
||||
|
||||
$this->regimen_fiscal_options = RegimenFiscal::selectList();
|
||||
$this->uso_cfdi_options = UsoCfdi::selectList();
|
||||
}
|
||||
|
||||
|
||||
public function reloadUserData()
|
||||
{
|
||||
$this->tipo_persona = $this->user->tipo_persona;
|
||||
$this->name = $this->user->name;
|
||||
$this->cargo = $this->user->cargo;
|
||||
$this->is_prospect = $this->user->is_prospect? true : false;
|
||||
$this->is_customer = $this->user->is_customer? true : false;
|
||||
$this->is_provider = $this->user->is_provider? true : false;
|
||||
$this->is_user = $this->user->is_user? true : false;
|
||||
$this->pricelist_id = $this->user->pricelist_id;
|
||||
$this->enable_credit = $this->user->enable_credit? true : false;
|
||||
$this->credit_days = $this->user->credit_days;
|
||||
$this->credit_limit = $this->user->credit_limit;
|
||||
$this->profile_photo = $this->user->profile_photo_url;
|
||||
$this->profile_photo_path = $this->user->profile_photo_path;
|
||||
$this->image = null;
|
||||
$this->deleteUserImage = false;
|
||||
|
||||
$this->status = $this->user->status;
|
||||
$this->email = $this->user->email;
|
||||
$this->password = null;
|
||||
$this->password_confirmation = null;
|
||||
|
||||
$this->rfc = $this->user->rfc;
|
||||
$this->domicilio_fiscal = $this->user->domicilio_fiscal;
|
||||
$this->nombre_fiscal = $this->user->nombre_fiscal;
|
||||
$this->c_regimen_fiscal = $this->user->c_regimen_fiscal;
|
||||
$this->c_uso_cfdi = $this->user->c_uso_cfdi;
|
||||
|
||||
$this->cuentaUsuarioAlert = null;
|
||||
$this->accesosAlert = null;
|
||||
$this->facturacionElectronicaAlert = null;
|
||||
}
|
||||
|
||||
|
||||
public function saveCuentaUsuario()
|
||||
{
|
||||
try {
|
||||
// Validar Información de usuario
|
||||
$validatedData = $this->validate($this->rulesUser);
|
||||
|
||||
$validatedData['name'] = trim($validatedData['name']);
|
||||
$validatedData['cargo'] = $validatedData['cargo']? trim($validatedData['cargo']): null;
|
||||
$validatedData['is_prospect'] = $validatedData['is_prospect'] ? 1 : 0;
|
||||
$validatedData['is_customer'] = $validatedData['is_customer'] ? 1 : 0;
|
||||
$validatedData['is_provider'] = $validatedData['is_provider'] ? 1 : 0;
|
||||
$validatedData['is_user'] = $validatedData['is_user'] ? 1 : 0;
|
||||
$validatedData['pricelist_id'] = $validatedData['pricelist_id'] ?: null;
|
||||
$validatedData['enable_credit'] = $validatedData['enable_credit'] ? 1 : 0;
|
||||
$validatedData['credit_days'] = $validatedData['credit_days'] ?: null;
|
||||
$validatedData['credit_limit'] = $validatedData['credit_limit'] ?: null;
|
||||
|
||||
if($this->tipo_persona == User::TIPO_RFC_PUBLICO){
|
||||
$validatedData['cargo'] = null;
|
||||
$validatedData['is_prospect'] = null;
|
||||
$validatedData['is_provider'] = null;
|
||||
$validatedData['is_user'] = null;
|
||||
$validatedData['enable_credit'] = null;
|
||||
$validatedData['credit_days'] = null;
|
||||
$validatedData['credit_limit'] = null;
|
||||
}
|
||||
|
||||
if(!$this->user->is_prospect && !$this->user->is_customer){
|
||||
$validatedData['pricelist_id'] = null;
|
||||
}
|
||||
|
||||
if(!$this->user->is_customer){
|
||||
$validatedData['enable_credit'] = null;
|
||||
$validatedData['credit_days'] = null;
|
||||
$validatedData['credit_limit'] = null;
|
||||
}
|
||||
|
||||
$this->user->update($validatedData);
|
||||
|
||||
|
||||
if($this->deleteUserImage && $this->user->profile_photo_path){
|
||||
$this->user->deleteProfilePhoto();
|
||||
|
||||
// Reiniciar variables después de la eliminación
|
||||
$this->deleteUserImage = false;
|
||||
$this->profile_photo_path = null;
|
||||
$this->profile_photo = $this->user->profile_photo_url;
|
||||
|
||||
}else if ($this->image) {
|
||||
$image = ImageManager::imagick()->read($this->image->getRealPath());
|
||||
$image = $image->scale(520, 520);
|
||||
|
||||
$imageName = $this->image->hashName(); // Genera un nombre único
|
||||
|
||||
$image->save(storage_path('app/public/profile-photos/' . $imageName));
|
||||
|
||||
$this->user->deleteProfilePhoto();
|
||||
|
||||
$this->profile_photo_path = $this->user->profile_photo_path = 'profile-photos/' . $imageName;
|
||||
$this->profile_photo = $this->user->profile_photo_url;
|
||||
$this->user->save();
|
||||
|
||||
unlink($this->image->getRealPath());
|
||||
|
||||
$this->reset('image');
|
||||
}
|
||||
|
||||
// Puedes también devolver un mensaje de éxito si lo deseas
|
||||
$this->setAlert('Se guardó los cambios exitosamente.', 'cuentaUsuarioAlert');
|
||||
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
// Si hay errores de validación, los puedes capturar y manejar aquí
|
||||
$this->setAlert('Ocurrieron errores en la validación: ' . $e->validator->errors()->first(), 'cuentaUsuarioAlert', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
public function saveAccesos()
|
||||
{
|
||||
try {
|
||||
$validatedData = $this->validate([
|
||||
'status' => 'integer',
|
||||
'email' => ['required', 'email', 'unique:users,email,' . $this->user->id],
|
||||
'password' => ['nullable', 'string', 'min:6', 'max:32', 'confirmed'], // La regla 'confirmed' valida que ambas contraseñas coincidan
|
||||
], [
|
||||
'email.required' => 'El correo electrónico es obligatorio.',
|
||||
'email.email' => 'Debes ingresar un correo electrónico válido.',
|
||||
'email.unique' => 'Este correo ya está en uso.',
|
||||
'password.min' => 'La contraseña debe tener al menos 5 caracteres.',
|
||||
'password.max' => 'La contraseña no puede tener más de 32 caracteres.',
|
||||
'password.confirmed' => 'Las contraseñas no coinciden.',
|
||||
]);
|
||||
|
||||
// Si la validación es exitosa, continuar con el procesamiento
|
||||
$validatedData['email'] = trim($this->email);
|
||||
|
||||
if ($this->password)
|
||||
$validatedData['password'] = bcrypt($this->password);
|
||||
|
||||
else
|
||||
unset($validatedData['password']);
|
||||
|
||||
$this->user->update($validatedData);
|
||||
|
||||
$this->password = null;
|
||||
$this->password_confirmation = null;
|
||||
|
||||
// Puedes también devolver un mensaje de éxito si lo deseas
|
||||
$this->setAlert('Se guardó los cambios exitosamente.', 'accesosAlert');
|
||||
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
// Si hay errores de validación, los puedes capturar y manejar aquí
|
||||
$this->setAlert('Ocurrieron errores en la validación: ' . $e->validator->errors()->first(), 'accesosAlert', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
public function saveFacturacionElectronica()
|
||||
{
|
||||
try {
|
||||
// Validar Información fiscal
|
||||
$validatedData = $this->validate($this->rulesFacturacion);
|
||||
|
||||
$validatedData['rfc'] = strtoupper(trim($validatedData['rfc'])) ?: null;
|
||||
$validatedData['domicilio_fiscal'] = $validatedData['domicilio_fiscal'] ?: null;
|
||||
$validatedData['nombre_fiscal'] = strtoupper(trim($validatedData['nombre_fiscal'])) ?: null;
|
||||
$validatedData['c_regimen_fiscal'] = $validatedData['c_regimen_fiscal'] ?: null;
|
||||
$validatedData['c_uso_cfdi'] = $validatedData['c_uso_cfdi'] ?: null;
|
||||
|
||||
$this->user->update($validatedData);
|
||||
|
||||
// Puedes también devolver un mensaje de éxito si lo deseas
|
||||
$this->setAlert('Se guardó los cambios exitosamente.', 'facturacionElectronicaAlert');
|
||||
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
// Si hay errores de validación, los puedes capturar y manejar aquí
|
||||
$this->setAlert('Ocurrieron errores en la validación: ' . $e->validator->errors()->first(), 'facturacionElectronicaAlert', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function setAlert($message, $alertName, $type = 'success')
|
||||
{
|
||||
$this->$alertName = [
|
||||
'message' => $message,
|
||||
'type' => $type
|
||||
];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.admin.crm.contact-view');
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user