first commit

This commit is contained in:
2025-03-05 21:11:33 -06:00
commit ac40d0f399
46 changed files with 6283 additions and 0 deletions

View File

@ -0,0 +1,519 @@
export default class AddressFormHandler {
constructor(formSelectors, ajaxRoutes, livewireInstance, csrfToken) {
if (!ajaxRoutes && !ajaxRoutes.estado) {
throw new Error("ajax AddressFormHandler routes not found");
}
this.formSelectors = formSelectors;
this.ajaxRoutes = ajaxRoutes;
this.livewireInstance = livewireInstance; // Livewire Instance
this.csrfToken = csrfToken;
this.placeholders = {
pais: 'Selecciona el país',
estado: 'Selecciona el estado',
localidad: 'Selecciona la localidad',
municipio: 'Selecciona el municipio',
colonia: 'Selecciona la colonia',
codigo_postal: 'Ingresa el código postal',
}
this.synchronizedZipCode = false;
this.initializeSelects();
this.initializeZipCodeInput();
this.loadDefaultValuesFromLivewire();
}
initializeSelects() {
const { c_codigo_postal, c_pais, c_estado, c_localidad, c_municipio, c_colonia } = this.formSelectors;
// País
$(c_pais)
.select2({
language: "es",
placeholder: this.placeholders.pais,
allowClear: true,
width: "100%"
})
.on('select2:select', (e) => this.onCountrySelect(e))
.on('select2:clear', () => {
this.resetFieldWithDependencies('c_pais');
});
// Estado
$(c_estado)
.select2({
language: "es",
placeholder: this.placeholders.estado,
allowClear: true,
width: "100%"
})
.on('select2:select', (e) => this.onStateSelect(e))
.on('select2:clear', () => {
this.resetFieldWithDependencies('c_estado');
});
// Localidad
$(c_localidad)
.select2({
language: "es",
placeholder: this.placeholders.localidad,
allowClear: true,
width: "100%"
})
.on('select2:select', (e) => this.onLocalitySelect(e))
.on('select2:clear', () => {
this.resetFieldWithDependencies('c_localidad');
});
// Municipio
$(c_municipio)
.select2({
ajax: {
url: this.ajaxRoutes['municipio'],
type: "post",
delay: 250,
dataType: 'json',
data: (params) => ({
_token: this.csrfToken,
select2Mode: true,
searchTerm: params.term,
c_estado: $(c_estado).val(),
}),
processResults: (response) => ({
results: response
}),
cache: true
},
language: "es",
placeholder: this.placeholders.municipio,
allowClear: true,
width: "100%"
})
.on('select2:select', (e) => this.onMunicipalitySelect(e))
.on('select2:clear', () => {
this.resetFieldWithDependencies('c_municipio');
});
// Colonia
$(c_colonia)
.select2({
ajax: {
url: this.ajaxRoutes['colonia'],
type: "post",
delay: 250,
dataType: 'json',
data: (params) => ({
_token: this.csrfToken,
select2Mode: true,
searchTerm: params.term,
c_codigo_postal: $(c_codigo_postal).val(),
c_estado: $(c_estado).val(),
c_municipio: $(c_municipio).val(),
}),
processResults: (response) => ({
results: response
}),
cache: true
},
language: "es",
placeholder: this.placeholders.colonia,
allowClear: true,
width: "100%"
})
.on('select2:select', (e) => this.onColoniaSelect(e))
.on('select2:clear', () => {
this.livewireInstance.set('c_colonia', null, false);
});
}
initializeZipCodeInput() {
const { c_codigo_postal } = this.formSelectors;
let lastPostalCode = ''; // Para evitar solicitudes duplicadas
let debounceTimeout;
$(c_codigo_postal).on('input', () => {
const postalCode = $(c_codigo_postal).val();
clearTimeout(debounceTimeout); // Cancelar el temporizador anterior
this.synchronizedZipCode = false;
debounceTimeout = setTimeout(() => {
if (postalCode.length === 5 && postalCode !== lastPostalCode) {
lastPostalCode = postalCode;
this.fetchZipCode(postalCode);
}
}, 300); // Ejecutar después de 300 ms de inactividad
});
}
async fetchZipCode(postalCode) {
try {
const response = await this.makeAjaxPost(this.ajaxRoutes.codigo_postal, {
_token: this.csrfToken,
searchTerm: postalCode,
firstRow: true,
rawMode: true
});
// Realizar solicitud AJAX para obtener datos del código postal
if (response.c_codigo_postal) {
// Limpiamos Interface
this.resetFieldWithDependencies('c_codigo_postal', response.c_codigo_postal);
// Cargamos Estados
this.toggleSelector('c_estado', response.c_estado);
// Cargar localidades
this.fillSelectOptions('c_localidad', {[response.c_localidad]: response.localidad }, response.c_localidad);
// Cargar municipios
this.fillSelectOptions('c_municipio', {[response.c_municipio]: response.municipio}, response.c_municipio);
// Abrir select de colonia
this.openSelect2('c_colonia');
// Marcar como sincronizado el código postal
this.synchronizedZipCode = true;
} else {
const { notification } = this.formSelectors;
// Emitir una notificación simple desde JavaScript
window.livewireNotification.emitNotification({
message: `Código postal ${postalCode} no encontrado.`,
type: 'warning',
target: notification,
});
}
} catch (error) {
console.error("Error al obtener datos del código postal:", error);
}
}
onCountrySelect(event) {
const countryCode = event.params.data.id;
// Limpiamos Interface
this.resetFieldWithDependencies('c_pais', countryCode);
// Cargamos Interface de estados
this.fetchStates(countryCode);
}
async fetchStates(countryCode) {
try {
const response = await this.makeAjaxPost(this.ajaxRoutes.estado, {
_token: this.csrfToken,
c_pais: countryCode
});
// Realizamos solicitud AJAX para obtener datos de los estados
if (response) {
const { c_codigo_postal } = this.formSelectors;
// Cargar los estados
this.fillSelectOptions('c_estado', response);
// Ocultar y resetear los campos si no es México
if (countryCode === 'MEX') {
$('.if_local_address_show').show();
// Colocar el foco en el código postal
$(c_codigo_postal).focus();
} else {
$('.if_local_address_show').hide();
// Abrir select de estado
this.openSelect2('c_estado');
}
}
} catch (error) {
console.error("Error al cargar los estados:", error);
}
}
onStateSelect(event) {
const { c_pais, direccion } = this.formSelectors;
const stateId = event.params.data.id;
// Limpiar Interface
this.resetFieldWithDependencies('c_estado', stateId);
// Si es México
if($(c_pais).val() == 'MEX'){
// Cargar localidades
this.fetchLocalities(stateId);
}else{
// Colocar el foco en la dirección
$(direccion).focus();
}
}
async fetchLocalities(stateId) {
try {
const response = await this.makeAjaxPost(this.ajaxRoutes.localidad, {
_token: this.csrfToken,
c_estado: stateId,
limit: null,
});
if (response) {
const { c_localidad } = this.formSelectors;
// Cargar localidades
this.fillSelectOptions('c_localidad', response, c_localidad);
// Abrir select de localidad
this.openSelect2('c_localidad');
}
} catch (error) {
console.error("Error al cargar las localidades:", error);
}
}
onLocalitySelect(event) {
const locationId = event.params.data.id;
// Limpiar Interface
this.resetFieldWithDependencies('c_localidad', locationId);
// Abrir select de municipio
this.openSelect2('c_municipio');
}
onMunicipalitySelect(event) {
const municipalityId = event.params.data.id;
// Limpiar Interface
this.resetFieldWithDependencies('c_municipio', municipalityId);
// Habilitamos colonias
this.toggleSelector('c_colonia');
// Abrir select colonia
this.openSelect2('c_colonia');
}
onColoniaSelect(event) {
const coloniaId = event.params.data.id;
// Cargar colonia
this.fillSelectOptions('c_colonia', {[coloniaId]: event.params.data.text}, coloniaId);
// Actualizar código postal si no está sincronizado
if(!this.synchronizedZipCode){
this.fetchZipCodeByData(this.livewireInstance.c_estado, this.livewireInstance.c_municipio, coloniaId);
}
}
async fetchZipCodeByData(stateId, municipalityId, coloniaId) {
try {
const response = await this.makeAjaxPost(this.ajaxRoutes.colonia, {
_token: this.csrfToken,
c_estado: stateId,
c_municipio: municipalityId,
c_colonia: coloniaId,
firstRow: true,
rawMode: true,
});
if (response) {
const { c_codigo_postal, direccion } = this.formSelectors;
// Actualizar código postal si no está sincronizado
if($(c_codigo_postal).val() !== response.c_codigo_postal){
$(c_codigo_postal).val(response.c_codigo_postal);
// Actualizar en Livewire
this.livewireInstance.set('c_codigo_postal', response.c_codigo_postal, false);
}
// Marcar como sincronizado el código postal
this.synchronizedZipCode = true;
// Abrir select colonia
$(direccion).focus();
}
} catch (error) {
console.error("Error al cargar las localidades:", error);
}
}
fillSelectOptions(selector, data, selected) {
const placeholder = this.placeholders[selector] || 'Selecciona una opción';
const $selector = $(this.formSelectors[selector]);
// Actualizar las opciones directamente en Livewire
this.livewireInstance.set(`${selector}_options`, data, false);
// Limpiar y agregar las nuevas opciones al selector
$selector.empty().append(new Option(placeholder, '', true, true)); // Agregar el placeholder
// Agregar opciones obtenidas por AJAX
if (typeof data === 'object' && Object.keys(data).length > 0) {
$.each(data, (id, value) => {
$selector.append(new Option(value, id, false, id == selected));
});
}
// Actualizar el valor seleccionado si corresponde
if (selected) {
this.livewireInstance.set(selector, selected, false);
$selector.val(selected).trigger('change.select2');
}
// Habilitar el select
$selector.prop('disabled', false).trigger('change.select2');
}
toggleSelector(selector, selected = null) {
const $selector = $(this.formSelectors[selector]);
if(typeof selected === 'number' || typeof selected === 'string'){
this.livewireInstance.set(selector, selected, false);
$selector
.val(selected)
.trigger('change.select2');
}
$selector.prop('disabled', !(selected || $selector.val())).trigger('change.select2');
}
resetFieldWithDependencies(field, value = null) {
const dependencies = {
c_codigo_postal: ['c_localidad', 'c_municipio', 'c_colonia'],
c_pais: ['c_codigo_postal', 'c_estado', 'c_localidad', 'c_municipio', 'c_colonia'],
c_estado: ['c_codigo_postal', 'c_localidad', 'c_municipio', 'c_colonia'],
c_localidad: ['c_codigo_postal', 'c_municipio', 'c_colonia'],
c_municipio: ['c_codigo_postal', 'c_colonia']
};
const resetFields = (fields) => {
fields.forEach((key) => {
const field = this.formSelectors[key]; // Obtener el selector por clave
const placeholder = this.placeholders[key] || 'Selecciona una opción';
const $field = $(field);
// Limpiar valor en Livewire
if (this.livewireInstance[key] !== null) {
this.livewireInstance.set(key, null, false);
}
if ($field.is('select')) {
// Resetear select
$field.empty()
.append(new Option(placeholder, '', true, true))
.prop('disabled', true)
.trigger('change.select2'); // Actualizar select2
} else if ($field.is('input[type="text"]') || $field.is('input[type="number"]')) {
// Resetear input de texto o número
$field.val('');
} else {
console.warn(`El campo ${field} no es un input ni un select válido.`);
}
});
};
// Limpieza de campos dependientes
if (dependencies[field]) {
resetFields(dependencies[field]);
}
// Actualizar el valor del campo principal en Livewire
this.livewireInstance.set(field, value, false);
}
openSelect2(selector) {
const $selector = $(this.formSelectors[selector]);
$selector.prop('disabled', false).trigger('change.select2');
setTimeout(() => $selector.select2('open'), 100);
}
async makeAjaxPost(url, data) {
try {
const response = await $.post(url, {
...data,
_token: this.csrfToken
});
return response;
} catch (error) {
console.error(`Error al realizar la solicitud AJAX a ${url}:`, error);
window.livewireNotification.emitNotification({
message: `Error al intentar realizar la solicitud.`,
target: this.formSelectors.notification,
type: 'danger',
});
return null; // Devuelve null en caso de error para evitar llamadas infinitas
}
}
loadDefaultValuesFromLivewire() {
const { c_pais, c_estado, c_localidad, c_municipio, c_colonia, c_codigo_postal } = this.livewireInstance;
const { c_pais: paisSelector, c_estado: estadoSelector, c_localidad: localidadSelector, c_municipio: municipioSelector, c_colonia: coloniaSelector, c_codigo_postal: codigoPostalSelector } = this.formSelectors;
// Cargar país por defecto
if (c_pais && $(paisSelector).val() !== c_pais) {
$(paisSelector).val(c_pais).trigger('change.select2');
}
// Cargar estados por defecto si están disponibles
if (c_estado && $(estadoSelector).val() !== c_estado) {
$(estadoSelector).val(c_estado).trigger('change.select2');
}
// Si el país es México, mostrar campos de dirección
if (c_pais === 'MEX') {
$('.if_local_address_show').show();
if (c_localidad && $(localidadSelector).val() !== c_localidad) {
$(localidadSelector).val(c_localidad).trigger('change.select2');
}
if (c_municipio && $(municipioSelector).val() !== c_municipio) {
$(municipioSelector).val(c_municipio).trigger('change.select2');
}
if (c_colonia && $(coloniaSelector).val() !== c_colonia) {
$(coloniaSelector).val(c_colonia).trigger('change.select2');
}
if (c_codigo_postal && $(codigoPostalSelector).val() !== c_codigo_postal) {
$(codigoPostalSelector).val(c_codigo_postal);
}
} else {
$('.if_local_address_show').hide();
}
}
}
window.AddressFormHandler = AddressFormHandler;

View File

@ -0,0 +1,93 @@
import {routes} from '../../../../../laravel-vuexy-admin/resources/assets/js/bootstrap-table/globalConfig.js';
export const contactActionFormatter = (value, row, index) => {
if (!row.id) return '';
const showUrl = routes['admin.contact.show'].replace(':id', row.id);
const editUrl = routes['admin.contact.edit'].replace(':id', row.id);
const deleteUrl = routes['admin.contact.delete'].replace(':id', row.id);
return `
<div class="flex space-x-2">
<a href="${editUrl}" title="Editar" class="icon-button">
<i class="ti ti-edit"></i>
</a>
<a href="${deleteUrl}" title="Eliminar" class="icon-button">
<i class="ti ti-trash"></i>
</a>
<a href="${showUrl}" title="Ver" class="icon-button">
<i class="ti ti-eye"></i>
</a>
</div>
`.trim();
};
export const agentFormatter = (value, row, index) => {
if (!row.agent_name) return '';
const email = row.agent_email || 'Sin correo';
const userUrl = routes['admin.user.show'].replace(':id', row.id);
return `
<div class="flex flex-col">
<a href="${userUrl}" class="font-medium text-slate-600 hover:underline block text-wrap">${row.agent_name}</a>
<small class="text-muted">${email}</small>
</div>
`;
};
export const contactParentFormatter = (value, row, index) => {
if (!row.parent_name) return '';
const email = row.parent_email || 'Sin correo';
const showUrl = routes['admin.contact.show'].replace(':id', row.id);
return `
<div class="flex flex-col">
<a href="${showUrl}" class="font-medium text-slate-600 hover:underline block text-wrap">${row.parent_name}</a>
<small class="text-muted">${email}</small>
</div>
`;
};
export const emailFormatter = (value, row, index) => {
if (!value) return '';
return `
<a href="mailto:${value}" class="flex items-center space-x-2 text-blue-600 hover:underline">
<i class="fa-solid fa-envelope"></i>
<span>${value}</span>
</a>
`;
};
export const telFormatter = (value, row, index) => {
if (!value) return '';
return `
<a href="tel:${value}" class="flex items-center space-x-2 text-green-600 hover:underline">
<i class="fa-solid fa-phone"></i>
<span class="whitespace-nowrap">${value}</span>
</a>
`;
};
export const direccionFormatter = (value, row, index) => {
let direccion = row.direccion ? row.direccion.trim() : '';
let numExt = row.num_ext ? ` #${row.num_ext}` : '';
let numInt = row.num_int ? `, Int. ${row.num_int}` : '';
let fullAddress = `${direccion}${numExt}${numInt}`.trim();
return fullAddress ? `<span class="whitespace-nowrap">${fullAddress}</span>` : '<span class="text-muted">Sin dirección</span>';
};

View File

@ -0,0 +1,29 @@
@props([
'uid' => uniqid(),
'paisOptions' => [],
'estadoOptions' => [],
'localidadOptions' => [],
'municipioOptions' => [],
'coloniaOptions' => [],
])
<x-vuexy-admin::card.basic title="Dirección">
<div class="address-notification"></div>
<div class="row">
<x-vuexy-admin::form.select :uid="$uid" model="c_pais" label="País" parentClass="col-8" :options="$paisOptions" />
<x-vuexy-admin::form.input :uid="$uid" model="c_codigo_postal" label="Código Postal" max="5" align="center" parentClass="col-4 if_local_address_show" />
</div>
<x-vuexy-admin::form.select :uid="$uid" model="c_estado" label="Estado" :options="$estadoOptions" />
<x-vuexy-admin::form.select :uid="$uid" model="c_localidad" label="Localidad" :options="$localidadOptions" parentClass="if_local_address_show" />
<x-vuexy-admin::form.select :uid="$uid" model="c_municipio" label="Municipio" :options="$municipioOptions" parentClass="if_local_address_show" />
<x-vuexy-admin::form.select :uid="$uid" model="c_colonia" label="Colonia" :options="$coloniaOptions" parentClass="if_local_address_show" />
<x-vuexy-admin::form.input :uid="$uid" model="direccion" label="Dirección" />
<div class="row">
<x-vuexy-admin::form.input :uid="$uid" model="num_ext" label="Número exterior" parentClass="col-6" />
<x-vuexy-admin::form.input :uid="$uid" model="num_int" label="Número interior" parentClass="col-6" />
</div>
</x-vuexy-admin::card.basic>

View File

@ -0,0 +1,15 @@
@props([
'uid' => uniqid(),
'mapId' => 'geo_map',
'mapHeight' => '400px',
'searchPlaceholder' => 'Buscar ubicación',
])
<x-vuexy-admin::card.basic title="Ubicación">
<x-vuexy-admin::form.textarea :uid="$uid" name="location_search" label="Dirección de búsqueda" :placeholder="$searchPlaceholder" rows="2" button-icon="ti ti-map-pin-search" onClickButton="clearCoordinates()" />
<div class="row">
<x-vuexy-admin::form.input :uid="$uid" model="lat" label="Latitud" type="number" step="0.000001" max="90" min="-90" parentClass="col-6" align="center" size="small" />
<x-vuexy-admin::form.input :uid="$uid" model="lng" label="Longitud" type="number" step="0.000001" max="180" min="-180" parentClass="col-6" align="center" size="small" />
</div>
<div style="height: {{ $mapHeight }}; z-index: 1;" id="locationMap_{{ $uid }}"></div>
</x-vuexy-admin::card.basic>

View File

@ -0,0 +1,28 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', $contacto->name)
@section('vendor-style')
@vite([
'resources/assets/admin/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
'resources/assets/admin/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
'resources/assets/admin/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
'resources/assets/admin/vendor/libs/select2/select2.scss',
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
])
@endsection
@section('content')
@livewire('contact-form', ['userId' => $contacto->id])
@endsection
@section('vendor-script')
@vite([
'resources/assets/admin/vendor/libs/moment/moment.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
'resources/assets/admin/vendor/libs/select2/select2.js',
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
])
@endsection

View File

@ -0,0 +1,31 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Contactos')
@section('vendor-style')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/dropzone/dropzone.scss',
])
@endsection
@section('vendor-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js',
])
@endsection
@push('page-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/dropzone/dropzone.js',
])
@endpush
@section('content')
@livewire('contact-index')
@livewire('contact-offcanvas-form')
@endsection

View File

@ -0,0 +1,28 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', $contact->name)
@section('vendor-style')
@vite([
'resources/assets/admin/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
'resources/assets/admin/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
'resources/assets/admin/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
'resources/assets/admin/vendor/libs/select2/select2.scss',
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
])
@endsection
@section('content')
@endsection
@section('vendor-script')
@vite([
'resources/assets/admin/vendor/libs/moment/moment.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
'resources/assets/admin/vendor/libs/select2/select2.js',
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
])
@endsection

View File

@ -0,0 +1,28 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Empleados')
@section('vendor-style')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss',
])
@endsection
@section('vendor-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js',
])
@endsection
@push('page-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js',
])
@endpush
@section('content')
@livewire('employee-index')
@endsection

View File

@ -0,0 +1,30 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', $contacto->name)
@section('vendor-style')
@vite([
'resources/assets/admin/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
'resources/assets/admin/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
'resources/assets/admin/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
'resources/assets/admin/vendor/libs/select2/select2.scss',
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
])
@endsection
@section('content')
<section class="crm-contacts-show">
@livewire('admin.crm.contact-view', ['userId' => $contacto->id])
</section>
@endsection
@section('vendor-script')
@vite([
'resources/assets/admin/vendor/libs/moment/moment.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
'resources/assets/admin/vendor/libs/select2/select2.js',
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
])
@endsection

View File

@ -0,0 +1,586 @@
<section id="crm-contacts-index">
<div class="crm-contacts-index alert-errors"></div>
<div wire:ignore>
<div class="query-filters" id="toolbar">
<div class="d-flex flex-wrap">
<div class="pt-1 pr-2 pb-1" style="min-width: 175px; max-width: 225px">
<button data-bs-toggle='offcanvas' data-bs-target='#offcanvasUser' class="btn btn-primary waves-effect waves-light">Agregar contacto</button>
</div>
<div class="accordion mr-3 mb-3" id="accordionParentFiltrado">
<div class="card accordion-item">
<h2 class="accordion-header" id="headingFiltrado">
<button type="button" class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#accordionFiltrado" aria-expanded="false" aria-controls="accordionFiltrado">
Filtrado
</button>
</h2>
<div id="accordionFiltrado" class="accordion-collapse collapse" aria-labelledby="headingFiltrado" data-bs-parent="#accordionParentFiltrado">
<div class="accordion-body">
<div class="d-flex flex-wrap">
<div class="pr-2 pl-4">
<div class="form-check form-check-secondary mb-0">
<input class="form-check-input" type="checkbox" id="filter_is_prospect" checked="checked">
<label class="form-check-label" for="filter_is_prospect">Es prospecto</label>
</div>
<div class="form-check form-check-secondary mb-0">
<input class="form-check-input" type="checkbox" id="filter_is_prospect" checked="checked">
<label class="form-check-label" for="filter_is_customer">Es cliente</label>
</div>
<div class="form-check form-check-secondary m-0">
<input class="form-check-input" type="checkbox" id="filter_is_provider" checked="checked">
<label class="form-check-label" for="filter_is_provider">Es proveedor</label>
</div>
</div>
<div class="pr-2 pl-4">
<div class="form-check form-check-secondary m-0">
<input class="form-check-input" type="checkbox" id="filter_is_user">
<label class="form-check-label" for="filter_is_user">Es usuario</label>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="px-2 pt-1">
<button class="btn btn-label-secondary waves-effect py-3 btn-refresh" disabled><i class="fa-solid fa-rotate"></i></button>
</div>
<div class="pr-2" style="width: 60px;">
<a href="javascript:void(0)" class="clear-filters">Limpiar Filtrado</a>
</div>
</div>
</div>
<table id="bt-contacts"></table>
</div>
</section>
@push('page-script')
<script>
const store_route = '{{ route('admin.system.users.store') }}',
route_show = '{{ route('admin.contacts.show', ['contact' => '~contact~']) }}',
route_destroy = '{{ route('admin.contacts.destroy', ['contact' => '~contact~']) }}',
statusList = {!! json_encode($status_list) !!},
statusIntCatalogCss = {!! json_encode($status_list_class) !!};
var btt_height;
// BootstrapTable Petición AJAX
function ajaxRequest(params) {
let url = '{{ url()->current() }}' +
'?' +
$.param(params.data) +
'&' +
$('.query-filters :input').serialize();
$.get(url).then(function (res) {
params.success(res)
})
}
// BootstrapTable Formatter
function userAvatarFormatter(value, row, index) {
if(row.id){
let show_href = route_show.replace('~contacto~', row.id);
return [
'<div class="d-flex justify-content-start align-items-center user-name">',
'<div class="avatar-wrapper">',
'<div class="avatar avatar-sm me-4">' +
'<img src="' + row.profile_photo_url + '" alt="Avatar" class="rounded-circle">' +
'</div>',
'</div>',
'<div class="d-flex flex-column">',
'<a href="' + show_href + '" class="text-heading text-truncate"><span class="fw-medium">' + row.name + '</span></a>',
'<small>' + row.email + '</small>',
'</div>',
'</div>',
].join('')
}
}
function uidFormatter(value, row, index) {
if(row.id){
let show_href = route_show.replace('~contacto~', row.id);
return [
'<div class="d-flex align-items-center justify-content-center">',
'<a href="' + show_href + '" class="whitespace-nowrap" title="Ver Contacto"> ' + row.id + '</a>',
@can('crm.contacts.update')
'<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill dropdown-toggle hide-arrow" data-bs-toggle="dropdown"><i class="ti ti-dots-vertical ti-md"></i></a>',
'<div class="dropdown-menu dropdown-menu-end m-0">',
'<a href="javascript:deleteRow(' + row.id + ');" class="dropdown-item delete-record">Eliminar</a>',
'</div>',
@endcan
'</div>'
].join('')
}
}
// BootstrapTable Init
function initTable(table) {
$(table)
.bootstrapTable('destroy')
.bootstrapTable({
height: btt_height,
locale: 'es-MX',
ajax: "ajaxRequest",
toolbar: "#toolbar",
search: true,
showColumns: true,
showColumnsToggleAll: true,
showExport: true,
showFullscreen: true,
showPaginationSwitch: true,
showRefresh: true,
showToggle: true,
clickToSelect: true,
minimumCountColumns: 4,
fixedColumns: true,
fixedNumber: 1,
idField: "id",
pagination: true,
pageList: [10, 25, 50, 100, 500],
sidePagination: "server",
exportTypes: ['csv', 'txt', 'excel'],
exportOptions: {
fileName: 'Contactos',
},
sortName: 'users.id',
sortOrder: 'desc',
mobileResponsive: true,
cookie: true,
resizable: true,
cookieIdTable:"crm-contacts-index",
columns: [
{
field: 'id',
title: 'UID',
align: 'center',
sortable: true,
sortName: 'users.id',
switchable: false,
formatter: uidFormatter
},
{
field: 'name',
title: 'Nombre',
formatter: userAvatarFormatter,
},
{
field: 'email',
title: 'Correo electrónico',
sortable: true,
visible: false,
},
{
field: 'tipo_persona',
title: 'Tipo persona',
visible: false,
sortable: true,
formatter: window.btFormatter.tipoPersona,
},
{
field: 'rfc',
title: 'RFC',
sortable: true,
},
{
field: 'nombre_fiscal',
title: 'Nombre fiscal',
sortable: true,
},
{
field: 'c_regimen_fiscal',
title: 'Regimen fiscal',
sortable: true,
visible: false,
formatter: window.btFormatter.regimenFiscal,
},
{
field: 'domicilio_fiscal',
title: 'Domicilio fiscal',
sortable: true,
visible: false,
},
{
field: 'estado',
title: 'Estado',
sortable: true,
},
{
field: 'municipio',
title: 'Municipio',
sortable: true,
},
{
field: 'localidad',
title: 'Localidad',
sortable: true,
visible: false,
},
{
field: 'c_uso_cfdi',
title: 'Uso de CFDI',
sortable: true,
visible: false,
formatter: window.btFormatter.usoCfdi,
},
{
field: 'cargo',
title: 'Cargo',
visible: false,
sortable: true,
},
{
field: 'is_prospect',
title: 'Es prospecto',
align: 'center',
visible: false,
sortable: true,
formatter: window.btFormatter.check,
},
{
field: 'is_customer',
title: 'Es cliente',
align: 'center',
visible: false,
sortable: true,
formatter: window.btFormatter.check,
},
{
field: 'is_provider',
title: 'Es proveedor',
align: 'center',
visible: false,
sortable: true,
formatter: window.btFormatter.check,
},
{
field: 'is_user',
title: 'Es usuario',
align: 'center',
visible: false,
sortable: true,
formatter: window.btFormatter.check,
},
{
field: 'status',
title: 'Estado',
align: 'center',
sortable: true,
formatter: window.btFormatter.status,
},
{
field: 'created_at',
title: 'Creado &nbsp; &nbsp; &nbsp; &nbsp;',
align: 'center',
sortable: true,
visible: false,
},
{
field: 'created_by_name',
title: 'Creado por',
align: 'center',
sortable: true,
visible: false,
sortName: 'created_by_name',
},
{
field: 'updated_at',
title: 'Modificado &nbsp; ',
align: 'center',
sortable: true,
visible: false,
},
]
})
}
function toggleSections() {
const isProspect = $('#is_prospect').is(':checked');
const isCustomer = $('#is_customer').is(':checked');
const isProvider = $('#is_provider').is(':checked');
const isUser = $('#is_user').is(':checked');
$('.div-sat').toggle(isCustomer || isProvider);
$('.div-sat-customer').toggle(isCustomer);
$('.div-user-auth').toggle(isCustomer || isUser);
$('.div-roles').toggle(isUser);
}
function toggleCheckboxes(status) {
const isDisabled = status == 1;
$('#is_prospect, #is_customer, #is_provider, #is_user').prop('disabled', isDisabled);
}
load_js_form = () => {
$('#userForm .select2')
.each(function() {
var $this = $(this)
$this.wrap('<div class="position-relative"></div>')
$this.select2({
dropdownAutoWidth: true,
width: '100%',
dropdownParent: $this.parent()
});
});
$("#pdf-dropzone")
.dropzone({
url: '{{ route('admin.contacts.extraer-datos-pdf-constancia') }}',
paramName: "file",
maxFiles: 1,
acceptedFiles: '.pdf',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
success: function(file, response) {
if($('#name').val().trim() == '')
@this.set('name', response.nombre_fiscal, false);
@this.set('rfc', response.rfc, false);
@this.set('nombre_fiscal', response.nombre_fiscal, false);
if($('#email').val().trim() == '' && response.email)
@this.set('email', response.email, false);
if(response.c_regimen_fiscal)
@this.set('c_regimen_fiscal', response.c_regimen_fiscal, false);
@this.set('domicilio_fiscal', response.domicilio_fiscal, false);
$('.pdf-dropzone-div').slideUp(200);
},
error: function(file, response) {
$('.pdf-dropzone-div .error-message').html('<label class="error">' + response + '</label>');
this.removeAllFiles(true);
}
});
// Evento para los checkboxes
$('#is_prospect, #is_customer, #is_provider, #is_user').on('change', toggleSections);
// Evento para el select de estado
$('.div-status').on('change', 'select[name="status"]', function() {
toggleCheckboxes($(this).val());
});
// Previo de imagenes
document.getElementById("photo").addEventListener('change', updatePreviewImage);
// Reset form
$("#userForm")
.on('reset', function(){
var form = $("#userForm");
form.validate().resetForm();
setTimeout(function(){
$('#roles').trigger('change');
toggleSections();
}, 250)
$('#user-image').prop("src", "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
$('#userForm .alert-errors').html('');
$('.pdf-dropzone-div').show();
$("#pdf-dropzone").removeAllFiles(true);
});
$("#userForm")
.validate({
errorClass: 'error',
highlight: function(element, errorClass, validClass) {
// Agrega la clase de error a la fila (contenedor del campo)
$(element).closest('.mb-3').addClass('has-error');
},
unhighlight: function(element, errorClass, validClass) {
// Elimina la clase de error de la fila (contenedor del campo)
$(element).closest('.mb-3').removeClass('has-error');
},
errorPlacement: function(error, element) {
// Controla dónde se colocan los mensajes de error
error.appendTo(element.closest('.mb-3').find('.error-message'));
},
rules: {
name: {
required: true,
minlength: 5
},
email: {
required: true,
email: true
},
password: {
required: function(element) {
return !$("#userForm input[name=id]").val() && ($('#is_user').is(':checked') || $('#is_customer').is(':checked'));
},
minlength: 6
}
},
messages: {
name: {
required: "Por favor ingrese su nombre completo",
minlength: "El nombre completo debe tener al menos 8 caracteres"
},
email: {
required: "Por favor ingrese su correo electrónico",
email: "El valor no es una dirección de correo válida"
},
password: {
required: "La contraseña es obligatoria para nuevos usuarios",
minlength: "La contraseña debe tener al menos 6 caracteres"
}
},
submitHandler: function(form, event) {
// Evita que el formulario se envíe automáticamente
event.preventDefault();
var form = $("#userForm")[0],
data = new FormData(form);
$('#userForm :input').prop('disabled', true);
$('#userForm .alert-errors').html('');
$.ajax({
url: store_route,
method: 'POST',
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
data: data,
contentType: false,
processData: false,
cache: false,
timeout: 3000,
success: function(data) {
$('#userForm :input').prop('disabled', false);
if (data.errors) {
$('#userForm .alert-errors').html('<div class="alert alert-danger alert-dismissible fade show" role="alert">' +
'<div class="alert-body">' + data.errors + '</div>' +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
'</div>');
} else {
let $usersIndexAlert = $('.crm-contacts-index.alert-errors');
$usersIndexAlert.html('<div class="alert alert-success alert-dismissible fade show" role="alert">' +
'<div class="alert-body">' +
'<p class="mb-0"><strong>' + data.success + '</strong></p>' +
'</div>' +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
'</div>');
$('#userForm button[type=reset]').trigger('click');
$('#toolbar .clear-filters').trigger('click');
}
},
error: function(e) {
$('#userForm :input').prop('disabled', false);
$('#userForm .alert-errors').html('<div class="alert alert-danger alert-dismissible fade show" role="alert">' +
'<div class="alert-body">' + e.responseJSON.message + '</div>' +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
'</div>');
}
});
}
});
// Inicializar el estado al cargar la página
toggleSections();
toggleCheckboxes($('select[name="status"]').val());
}
// Previo de imagen de perfil
updatePreviewImage = (event) => {
var file = event.target.files[0],
reader = new FileReader();
reader.onload = event => {
document.getElementById('user-image').setAttribute('src', event.target.result);
};
reader.readAsDataURL(file);
}
document.addEventListener('DOMContentLoaded', function() {
$(document).ready(function() {
var $table = $('#bt-contacts'),
$btnRefresh = $('#toolbar .btn-refresh'),
$clearFilters = $('a.clear-filters');
var btt_rest_height = 220,
btt_min_height = 600;
btt_height = (window.innerHeight - btt_rest_height) < btt_min_height?
btt_height:
window.innerHeight - btt_rest_height;
var offcanvasElement = document.getElementById('offcanvasUser'),
offcanvasUser = new bootstrap.Offcanvas(offcanvasElement);
const refreshButton = document.querySelector('.btn-refresh');
const inputs = document.querySelectorAll('#toolbar input, #toolbar select');
inputs.forEach(input => {
input.addEventListener('change', () => {
refreshButton.disabled = false;
refreshButton.classList.remove('btn-label-secondary');
refreshButton.classList.add('btn-label-success');
});
});
// Button Refresh
$btnRefresh
.on('click', () => {
$table.bootstrapTable('refresh');
refreshButton.disabled = true;
refreshButton.classList.remove('btn-label-success');
refreshButton.classList.add('btn-label-secondary');
});
// Button clear filters
$clearFilters
.on('click', () => {
$table.bootstrapTable('resetSearch', ''); // Inicializa la búsqueda con cadena vacía
refreshButton.disabled = true;
refreshButton.classList.remove('btn-label-success');
refreshButton.classList.add('btn-label-secondary');
});
initTable('#bt-contacts'); // Una vez que todos los scripts estén cargados, inicializa Bootstrap Table
load_js_form();
});
});
</script>
@endpush

View File

@ -0,0 +1,165 @@
<div>
<x-vuexy-admin::form id="{{ $formId }}" :mode="$mode" wireSubmit="onSubmit" actionPosition="both">
<x-slot name="actions">
<x-vuexy-admin::button.offcanvas-buttons :mode="$mode" :tagName="$tagName" />
</x-slot>
<div class="row">
<div class="col-lg-4">
{{-- Identificación --}}
<x-vuexy-admin::card.basic title="Identificación">
<x-vuexy-admin::form.input :uid="$uniqueId" model="code" label="Identificador único" icon="ti ti-tag" placeholder="UID code" autofocus autocomplete="off" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="name" label="Nombre de la sucursal" autocomplete="organization" />
<x-vuexy-admin::form.textarea :uid="$uniqueId" model="description" label="Descripción" placeholder="Descripción de la sucursal" :autosize=true />
</x-vuexy-admin::card.basic>
{{-- Series de facturación --}}
<x-vuexy-admin::card.basic title="Series de facturación">
<x-vuexy-admin::form.input :uid="$uniqueId" model="serie_ingresos" label="Serie para Ingresos" inline=true :labelCol=6 :inputCol=6 maxlength="5" autocomplete="off" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="serie_egresos" label="Serie para Egresos" inline=true :labelCol=6 :inputCol=6 maxlength="5" autocomplete="off" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="serie_pagos" label="Serie para Pagos" inline=true :labelCol=6 :inputCol=6 maxlength="5" autocomplete="off" />
</x-vuexy-admin::card.basic>
{{-- Configuraciones --}}
<x-vuexy-admin::card.basic title="Configuraciones">
<x-vuexy-admin::form.checkbox uid="random" model="status" label="Habilitar sucursal" switch="true" />
<x-vuexy-admin::form.checkbox uid="random" model="show_on_website" label="Mostrar en sitio web" switch="true" />
<x-vuexy-admin::form.checkbox uid="random" model="enable_ecommerce" label="eCommerce habilitado en sitio Web" switch="true" />
</x-vuexy-admin::card.basic>
</div>
<div class="col-lg-4">
{{-- Información de contacto --}}
<x-vuexy-admin::card.basic title="Información de contacto">
<x-vuexy-admin::form.input type="tel" :uid="$uniqueId" model="tel" label="Teléfono" icon="ti ti-phone" phoneMode="national" />
<x-vuexy-admin::form.input type="tel" :uid="$uniqueId" model="tel2" label="Teléfono alternativo" icon="ti ti-phone" phoneMode="both" />
<x-vuexy-admin::form.input type="email" :uid="$uniqueId" model="email" label="Correo electrónico" icon="ti ti-mail" autocomplete="email" inputmode="email" />
<x-vuexy-admin::form.select :uid="$uniqueId" model="manager_id" label="Gerente" :options="$manager_id_options" placeholder="Selecciona el gerente" />
</x-vuexy-admin::card.basic>
{{-- Información fiscal --}}
<x-vuexy-admin::card.basic title="Información fiscal">
<x-vuexy-admin::form.input :uid="$uniqueId" model="rfc" label="RFC" autocomplete="off" pattern="^[A-Z&Ñ]{3,4}[0-9]{6}[A-Z0-9]{3}$" maxlength="13" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="nombre_fiscal" label="Nombre fiscal" autocomplete="organization" />
<x-vuexy-admin::form.select :uid="$uniqueId" model="c_regimen_fiscal" label="Régimen fiscal" :options="$c_regimen_fiscal_options" placeholder="Selecciona el régimen fiscal" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="domicilio_fiscal" label="Domicilio fiscal" autocomplete="address-line1" maxlength="100" />
</x-vuexy-admin::card.basic>
</div>
<div class="col-lg-4">
{{-- Dirección --}}
<x-vuexy-contacts::card.address :uid="$uniqueId" :paisOptions="$c_pais_options" :estadoOptions="$c_estado_options" :localidadOptions="$c_localidad_options" :municipioOptions="$c_municipio_options" :coloniaOptions="$c_colonia_options"/>
{{-- Ubicación --}}
<x-vuexy-contacts::card.location :uid="$uniqueId" />
</div>
</div>
</x-vuexy-admin::form>
</div>
@push('page-script')
<script>
const initializeStoreForm = (mode) => {
const initializeContactInformation = () => {
let $manager_id = $("#manager_id_{{ $uniqueId }}");
$manager_id
.select2({
language: "es",
placeholder: "Selecciona el gerente",
allowClear: true,
width: "100%"
})
.on('select2:select select2:clear', function (e) {
@this.manager_id = e.params?.data?.id || null;
});
}
const initializeFiscalInformation = () => {
let $c_regimen_fiscal = $("#c_regimen_fiscal_{{ $uniqueId }}");
$c_regimen_fiscal
.select2({
language: "es",
placeholder: "Selecciona el regimen fiscal",
allowClear: true,
width: "100%"
})
.on('select2:select select2:clear', function (e) {
@this.c_regimen_fiscal = e.params?.data?.id || null;
});
}
const initializeLocationIQ = () => {
//
}
const initializeAddressFormHandler = () => {
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Definición de selectores AddressFormHandler
formSelectors = {
c_pais: '#c_pais_{{ $uniqueId }}',
c_estado: '#c_estado_{{ $uniqueId }}',
c_localidad: '#c_localidad_{{ $uniqueId }}',
c_municipio: '#c_municipio_{{ $uniqueId }}',
c_colonia: '#c_colonia_{{ $uniqueId }}',
c_codigo_postal: '#c_codigo_postal_{{ $uniqueId }}',
direccion: '#direccion_{{ $uniqueId }}',
notification: '#{{ $formId }} .address-notification'
};
// Definición de rutas AJAX Componente AddressFormHandler
const ajaxRoutes = {
codigo_postal: "{{ route('admin.core.sat.get.ajax', 'codigo_postal') }}",
localidad: "{{ route('admin.core.sat.get.ajax', 'localidad') }}",
estado: "{{ route('admin.core.sat.get.ajax', 'estado') }}",
municipio: "{{ route('admin.core.sat.get.ajax', 'municipio') }}",
colonia: "{{ route('admin.core.sat.get.ajax', 'colonia') }}"
};
// Inicializamos el handler de la información de la dirección
new AddressFormHandler(formSelectors, ajaxRoutes, @this, csrfToken);
}
const initializeLocationCard = (mode) => {
const locationInputs = {
search: '#location_search_{{ $uniqueId }}',
btnSearch: '#btn_search_{{ $uniqueId }}',
lat: '#lat_{{ $uniqueId }}',
lng: '#lng_{{ $uniqueId }}',
btnClear: '#{{ $formId }} .btn-clear-coords',
mapId: 'locationMap_{{ $uniqueId }}',
}
leafletMap = LeafletMapHelper.initializeMap(locationInputs, mode, @this);
}
// Inicializamos Tarjeta de Información de contacto
initializeContactInformation();
// Inicializamos Tarjeta de Información fiscal
initializeFiscalInformation();
// Inicializamos Tarjeta de Dirección
initializeAddressFormHandler();
// Inicializamos Tarjeta de Ubicación
initializeLocationCard(mode);
// Deshabilitamos el formulario si estamos eliminando
if (mode === 'delete') {
window.disableStoreForm('#{{ $formId }}');
}
}
// Evento para inicializar el formulario
document.addEventListener("DOMContentLoaded", () => {
document.addEventListener('on-failed-validation-store', (event) => {
setTimeout(() => {
initializeStoreForm('{{ $mode }}');
}, 10);
});
initializeStoreForm('{{ $mode }}');
});
</script>
@endpush

View File

@ -0,0 +1,57 @@
<x-vuexy-admin::table.bootstrap.manager :tagName="$tagName" :datatableConfig="$bt_datatable" :routes="$routes">
<x-slot name="tools">
<div class="mb-4 pr-2">
<x-vuexy-admin::button.index-off-canvas :label="$singularName" :tagName="$tagName" />
</div>
</x-slot>
<x-slot name="postTools">
<div class="mb-4 pr-2">
<x-vuexy-admin::file.dropzone id="user_doc_file" model="user_doc_file" message="Crear nuevo usuario" note="XML CFDI o PDF CSF" size="xs" />
</div>
</x-slot>
</x-vuexy-admin::table.bootstrap.manager>
@push('page-script')
<script>
// Evento para inicializar el formulario cuando se carga la página
document.addEventListener("DOMContentLoaded", function () {
let dropzone = new Dropzone("#dropzone_user_doc_file", {
url: "#",
autoProcessQueue: false,
acceptedFiles: ".pdf,.xml",
maxFiles: 1,
addRemoveLinks: true,
dictDefaultMessage: "Arrastra aquí tu PDF de Constancia de Situación Fiscal",
init: function () {
this.on("addedfile", function (file) {
let reader = new FileReader();
reader.onload = function () {
let input = document.querySelector("#user_doc_file");
let dataTransfer = new DataTransfer();
dataTransfer.items.add(new File([file], file.name, { type: file.type }));
// Asignamos solo un archivo, no un FileList
//input.files = dataTransfer.files;
// Livewire solo recibe archivos en forma de input, no como FileList
@this.upload('doc_file', dataTransfer.files[0],
(uploadedFile) => {
@this.call('processDocument');
},
(error) => {
console.error("Error al subir PDF:", error);
},
(progressEvent) => {
console.log("Progreso de subida:", progressEvent);
}
);
};
reader.readAsDataURL(file);
});
}
});
});
</script>
@endpush

View File

@ -0,0 +1,94 @@
<div>
<x-vuexy-admin::offcanvas.basic :id="$offcanvasId" :tag-name="$tagName">
{{-- Dropzone Constancia de Situación Fiscal --}}
<x-vuexy-admin::file.dropzone :uid="$uniqueId" model="doc_file" message="CSF PDF o CFDI XML" note="Arrastra aquí un PDF de Constancia de Situación Fiscal o XML de CFDI para cargar los datos al fomulario" />
<x-vuexy-admin::form :uid="$uniqueId" :id="$formId" :mode="$mode" wireSubmit="onSubmit" actionPosition="both">
<x-slot name="actions">
<x-vuexy-admin::button.offcanvas-buttons :mode="$mode" :tagName="$tagName" />
</x-slot>
{{-- Selección de Sucursal --}}
<x-vuexy-admin::form.input :uid="$uniqueId" model="name" label="Nombre(s)" />
<x-vuexy-admin::form.input :uid="$uniqueId" model="last_name" label="Apellidos" />
<hr>
<div class="row">
<x-vuexy-admin::form.input :uid="$uniqueId" model="code" label="Código de usuario" icon="ti ti-tag" parent-class="col-md-8" autocomplete="off" />
</div>
{{-- Teléfonos y Correos --}}
<x-vuexy-admin::form.input type="email" :uid="$uniqueId" model="email" label="Correo electrónico" icon="ti ti-mail" autocomplete="email" inputmode="email" />
<x-vuexy-admin::form.input type="tel" :uid="$uniqueId" model="tel" label="Teléfono" icon="ti ti-phone" phoneMode="both" />
<hr>
<x-vuexy-admin::form.textarea :uid="$uniqueId" model="notes" label="Notas / Observaciones" />
<hr>
{{-- Estado del Centro de Trabajo --}}
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_partner" label="Es socio" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_employee" label="Es empleado" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_prospect" label="Es prospecto" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_customer" label="Es cliente" switch />
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_provider" label="Es proveedor" switch />
<hr>
</x-vuexy-admin::form>
</x-vuexy-admin::offcanvas.basic>
</div>
@push('page-script')
<script>
// Evento para inicializar el formulario cuando se carga la página
document.addEventListener("DOMContentLoaded", function () {
const initializeUserForm = () => {
let dropzone = new Dropzone("#dropzone_doc_file_{{ $uniqueId }}", {
url: "#",
autoProcessQueue: false,
acceptedFiles: ".pdf,.xml",
maxFiles: 1,
addRemoveLinks: true,
dictDefaultMessage: "Arrastra aquí tu PDF de Constancia de Situación Fiscal",
init: function () {
this.on("addedfile", function (file) {
let reader = new FileReader();
reader.onload = function () {
let input = document.querySelector("#doc_file_{{ $uniqueId }}");
let dataTransfer = new DataTransfer();
dataTransfer.items.add(new File([file], file.name, { type: file.type }));
// Asignamos solo un archivo, no un FileList
//input.files = dataTransfer.files;
// Livewire solo recibe archivos en forma de input, no como FileList
@this.upload('doc_file', dataTransfer.files[0],
(uploadedFile) => {
@this.call('processDocument');
},
(error) => {
console.error("Error al subir PDF:", error);
},
(progressEvent) => {
console.log("Progreso de subida:", progressEvent);
}
);
};
reader.readAsDataURL(file);
});
}
});
};
var myOffcanvas = document.getElementById('{{ $offcanvasId }}');
myOffcanvas.addEventListener('show.bs.offcanvas', function () {
initializeUserForm();
});
});
</script>
@endpush

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', 'Proveedores')
@section('vendor-style')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss',
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss',
])
@endsection
@section('vendor-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js',
])
@endsection
@push('page-script')
@vite([
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js',
'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js',
])
@endpush
@section('content')
@livewire('supplier-index')
@endsection

View File

@ -0,0 +1,27 @@
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
@section('title', $contacto->name)
@section('vendor-style')
@vite([
'resources/assets/admin/vendor/libs/select2/select2.scss',
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
])
@endsection
@section('content')
<section class="crm-contacts-show">
@livewire('admin.crm.contact-view', ['userId' => $contacto->id])
</section>
@endsection
@section('vendor-script')
@vite([
'resources/assets/admin/vendor/libs/moment/moment.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
'resources/assets/admin/vendor/libs/select2/select2.js',
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
])
@endsection