From 35e4e8ef95e7e3a21cd9976166e2c5acb591bb14 Mon Sep 17 00:00:00 2001 From: imnavajas Date: Tue, 2 Jul 2024 21:45:32 +0200 Subject: [PATCH 01/10] Configuraciones iniciales del sistema de mensajeria --- .../Mensajeria/ConversacionEntity.php | 28 + .../Mensajeria/ParticipanteEntity.php | 26 + .../Models/Mensajeria/ConversacionModel.php | 138 +++++ .../Models/Mensajeria/ParticipanteModel.php | 30 ++ .../cosidotapablanda/_mensajeria.php | 498 ++++++++++++++++++ .../viewCosidotapablandaForm.php | 5 +- 6 files changed, 724 insertions(+), 1 deletion(-) create mode 100644 ci4/app/Entities/Mensajeria/ConversacionEntity.php create mode 100644 ci4/app/Entities/Mensajeria/ParticipanteEntity.php create mode 100644 ci4/app/Models/Mensajeria/ConversacionModel.php create mode 100644 ci4/app/Models/Mensajeria/ParticipanteModel.php create mode 100644 ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php diff --git a/ci4/app/Entities/Mensajeria/ConversacionEntity.php b/ci4/app/Entities/Mensajeria/ConversacionEntity.php new file mode 100644 index 00000000..4ee45b16 --- /dev/null +++ b/ci4/app/Entities/Mensajeria/ConversacionEntity.php @@ -0,0 +1,28 @@ + null, + 'pedido_libro_id' => null, + 'pedido_maquetacion_id' => null, + 'factura_id' => null, + 'departamento' => null, + 'asunto' => null, + 'created_at' => null, + 'updated_at' => null, + 'deleted_at' => null, + ]; + + protected $casts = [ + "pedido_libro_id" => "?int", + "pedido_maquetacion_id" => "?int", + "factura_id" => "?int" + ]; + + +} diff --git a/ci4/app/Entities/Mensajeria/ParticipanteEntity.php b/ci4/app/Entities/Mensajeria/ParticipanteEntity.php new file mode 100644 index 00000000..6a38ebab --- /dev/null +++ b/ci4/app/Entities/Mensajeria/ParticipanteEntity.php @@ -0,0 +1,26 @@ + null, + 'conversacion_id' => null, + 'usuario_id' => null, + 'cliente_id' => null, + 'email' => null, + 'last_read' => null, + 'created_at' => null, + 'updated_at' => null, + 'deleted_at' => null, + ]; + + protected $casts = [ + "conversacion_id" => "?int", + "usuario_id" => "?int", + "cliente_id" => "?int" + ]; +} diff --git a/ci4/app/Models/Mensajeria/ConversacionModel.php b/ci4/app/Models/Mensajeria/ConversacionModel.php new file mode 100644 index 00000000..32aff33d --- /dev/null +++ b/ci4/app/Models/Mensajeria/ConversacionModel.php @@ -0,0 +1,138 @@ +db + ->table('pedido_libros') + ->where('id', $this->pedido_libro_id) + ->get() + ->getFirstRow(); + } + + public function pedidoMaquetacion() + { + return $this->db + ->table('pedido_maquetaciones') + ->where('id', $this->pedido_maquetacion_id) + ->get() + ->getFirstRow(); + } + + public function factura() + { + return $this->db + ->table('facturas') + ->where('id', $this->factura_id) + ->get() + ->getFirstRow(); + } + + /** + * Devuelve si la conversacion tiene algún cliente + */ + public function isClient() + { + $isCliente = false; + $participantes = $this->participantes(); // Asegúrate de implementar este método + foreach ($participantes as $p) { + if (($p->user && $p->user->customer_id) || $p->customer_id) { + $isCliente = true; + break; + } + } + + return $isCliente; + } + + /** + * Comprueba si la conversacion del mensaje tiene el cliente + */ + public function haveCliente($userCustomer) + { + $customer_id = $userCustomer->customer_id; + $participants = $this->participantes(); // Asegúrate de implementar este método + + foreach ($participants as $p) { + if ($p->customer_id && $p->customer_id === $customer_id) { + return true; + } + } + + return false; + } + + public function getParticipanteDesdeCliente($cliente_id) + { + return $this->db->table('chat_participantes') + ->where('cliente_id', $cliente_id) + ->where('conversacion_id', $this->id) + ->get() + ->getFirstRow(); + } + + public function marcarLeidoCliente($cliente_id) + { + try { + $participante = $this->getParticipanteDesdeCliente($cliente_id); + if ($participante) { + $this->db->table('chat_participantes') + ->where('id', $participante->id) + ->update(['last_read' => date('Y-m-d H:i:s')]); + } + } catch (\Exception $e) { + // do nothing + } + } + + public function marcarNoLeido($usuario_id) + { + try { + $participante = $this->getParticipanteDesdeUsuario($usuario_id); // Asegúrate de implementar este método + if ($participante) { + $this->db->table('chat_participantes') + ->where('id', $participante->id) + ->update(['last_read' => null]); + } + } catch (\Exception $e) { + // do nothing + } + } + + protected function participantes() + { + return $this->db + ->table('chat_participantes') + ->where('conversacion_id', $this->id) + ->get() + ->getResult(); + } + + protected function getParticipanteDesdeUsuario($usuario_id) + { + return $this->db->table('chat_participantes') + ->where('usuario_id', $usuario_id) + ->where('conversacion_id', $this->id) + ->get() + ->getFirstRow(); + } +} diff --git a/ci4/app/Models/Mensajeria/ParticipanteModel.php b/ci4/app/Models/Mensajeria/ParticipanteModel.php new file mode 100644 index 00000000..f20c177e --- /dev/null +++ b/ci4/app/Models/Mensajeria/ParticipanteModel.php @@ -0,0 +1,30 @@ +find($this->attributes['customer_id']); + } +} diff --git a/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php new file mode 100644 index 00000000..86abbe58 --- /dev/null +++ b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php @@ -0,0 +1,498 @@ +
+
+
+ + + + + + +
+
+
+
+
+ +
+ P +
+
+
Departamento Producción
+ Consulta sobre el presupuesto P001 +
+
+ +
+
+
+
    +
  • +
    +
    +
    +

    How can we help? We're here for you! 😄

    +
    +
    + + 10:00 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    Hey John, I am looking for the best admin template.

    +

    Could you please help me to find it out? 🤔

    +
    +
    +

    It should be Bootstrap 5 compatible.

    +
    +
    + 10:02 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    Vuexy has all the components you'll ever need in a app.

    +
    +
    + + 10:03 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    Looks clean and fresh UI. 😃

    +
    +
    +

    It's perfect for my next project.

    +
    +
    +

    How can I purchase it?

    +
    +
    + 10:05 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    Thanks, you can purchase it.

    +
    +
    + + 10:06 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    I will purchase it for sure. 👍

    +
    +
    +

    Thanks.

    +
    +
    + 10:08 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    Great, Feel free to get in touch.

    +
    +
    + + 10:10 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    Do you have design files for Vuexy?

    +
    +
    + 10:15 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    + Yes that's correct documentation file, Design files are included with + the template. +

    +
    +
    + + 10:15 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
+
+ + +
+
+ + +
+
+
+
+ + + + + +section("additionalInlineJs") ?> + +/** + * App Chat + */ + +// Seleccionar elementos del DOM +const chatContactsBody = document.querySelector('.app-chat-contacts .sidebar-body'), + chatContactListItems = [].slice.call( + document.querySelectorAll('.chat-contact-list-item:not(.chat-contact-list-item-title)') + ), + chatHistoryBody = document.querySelector('.chat-history-body'), + chatSidebarLeftUserAbout = document.querySelector('.chat-sidebar-left-user-about'), + messageInput = document.querySelector('.message-input'), + searchInput = document.querySelector('.chat-search-input'), + sendMsgBtn = document.querySelector('.send-msg-btn'); // Seleccionar el botón de envío de mensaje + +// Inicializar PerfectScrollbar +if (chatContactsBody) { + new PerfectScrollbar(chatContactsBody, { + wheelPropagation: false, + suppressScrollX: true + }); +} + +if (chatHistoryBody) { + new PerfectScrollbar(chatHistoryBody, { + wheelPropagation: false, + suppressScrollX: true + }); +} + +// Función para desplazar el scroll al final +function scrollToBottom() { + if (chatHistoryBody) { + chatHistoryBody.scrollTo(0, chatHistoryBody.scrollHeight); + } +} +scrollToBottom(); + +// Seleccionar chat o contacto +chatContactListItems.forEach(chatContactListItem => { + chatContactListItem.addEventListener('click', e => { + chatContactListItems.forEach(item => { + item.classList.remove('active'); + }); + e.currentTarget.classList.add('active'); + }); +}); + +// Filtrar chats +if (searchInput) { + searchInput.addEventListener('keyup', e => { + const searchValue = e.currentTarget.value.toLowerCase(), + chatListItem0 = document.querySelector('.chat-list-item-0'), + contactListItem0 = document.querySelector('.contact-list-item-0'), + searchChatListItems = [].slice.call( + document.querySelectorAll('#chat-list li:not(.chat-contact-list-item-title)') + ), + searchContactListItems = [].slice.call( + document.querySelectorAll('#contact-list li:not(.chat-contact-list-item-title)') + ); + + // Buscar en chats + const chatListItemsCount = searchChatContacts(searchChatListItems, searchValue); + // Mostrar u ocultar mensaje de "No se encontraron resultados" en chats + if (chatListItem0) { + chatListItem0.classList.toggle('d-none', chatListItemsCount !== 0); + } + + // Buscar en contactos + const contactListItemsCount = searchChatContacts(searchContactListItems, searchValue); + // Mostrar u ocultar mensaje de "No se encontraron resultados" en contactos + if (contactListItem0) { + contactListItem0.classList.toggle('d-none', contactListItemsCount !== 0); + } + }); +} + +// Función para buscar en chats y contactos +function searchChatContacts(searchListItems, searchValue) { + let searchListItemsCount = 0; + searchListItems.forEach(searchListItem => { + const searchListItemText = searchListItem.textContent.toLowerCase(); + const matchesSearch = searchListItemText.indexOf(searchValue) !== -1; + + searchListItem.classList.toggle('d-flex', matchesSearch); + searchListItem.classList.toggle('d-none', !matchesSearch); + + if (matchesSearch) { + searchListItemsCount++; + } + }); + + return searchListItemsCount; +} + +// Enviar mensaje +if (sendMsgBtn) { + sendMsgBtn.addEventListener('click', e => { + e.preventDefault(); + if (messageInput.value) { + const renderMsg = document.createElement('div'); + renderMsg.className = 'chat-message-text mt-2'; + renderMsg.innerHTML = `

${messageInput.value}

`; + const lastChatMessageWrapper = document.querySelector('li:last-child .chat-message-wrapper'); + if (lastChatMessageWrapper) { + lastChatMessageWrapper.appendChild(renderMsg); + } + messageInput.value = ''; + scrollToBottom(); + } + }); +} + + + + + + + +endSection() ?> + + diff --git a/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php index c0596ac0..5d3c296c 100644 --- a/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php +++ b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php @@ -35,7 +35,8 @@ - + + @@ -367,6 +368,7 @@ $('#bc-save').on( "click", function() { section('css') ?> "> + endSection() ?> @@ -390,5 +392,6 @@ $('#bc-save').on( "click", function() { + endSection() ?> From f46a03e66d2f9cadd7e7fbf85510db9d2e99143a Mon Sep 17 00:00:00 2001 From: imnavajas Date: Sat, 6 Jul 2024 22:44:29 +0200 Subject: [PATCH 02/10] avances de hoy --- ci4/app/Config/Routes.php | 17 + ci4/app/Controllers/Js_loader.php | 8 + .../Mensajeria/MensajesDirectos.php | 80 ++++ ci4/app/Controllers/Test.php | 54 ++- .../Models/Mensajeria/ConversacionModel.php | 63 +++ .../Models/Mensajeria/ParticipanteModel.php | 24 +- .../themes/vuexy/form/mensajes/mensajeria.js | 138 +++++++ .../vuexy/form/mensajes/mensajesView.php | 364 ++++++++++++++++++ .../themes/vuexy/main/menus/mensajes_menu.php | 2 +- 9 files changed, 720 insertions(+), 30 deletions(-) create mode 100644 ci4/app/Controllers/Mensajeria/MensajesDirectos.php create mode 100644 ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js create mode 100644 ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php diff --git a/ci4/app/Config/Routes.php b/ci4/app/Config/Routes.php index 01f07dbd..3abe4e05 100644 --- a/ci4/app/Config/Routes.php +++ b/ci4/app/Config/Routes.php @@ -690,6 +690,23 @@ $routes->group( ); $routes->resource('buscadorpresupuestos', ['namespace' => 'App\Controllers\Presupuestos', 'controller' => 'Buscador', 'except' => 'show,new,create,update']); +/* Rutas para mensajeria */ +$routes->group('mensajes', ['namespace' => 'App\Controllers\Mensajeria'], function ($routes) { + + /* Interna */ + $routes->group('internos', ['namespace' => 'App\Controllers\Mensajeria'], function ($routes) { + + $routes->get('', 'MensajesDirectos::index', ['as' => 'mensajeriaView']); + $routes->get('chat/(:num)', 'MensajesDirectos::getChatInfo/$1', ['as' => 'getChatInfo']); + /*$routes->match(['get', 'post'], 'add', 'TarifaAcabados::add', ['as' => 'tarifaAcabadoAdd']); + $routes->match(['get', 'post'], 'edit/(:num)', 'TarifaAcabados::edit/$1', ['as' => 'tarifaAcabadoEdit']); + $routes->get('delete/(:num)', 'TarifaAcabados::delete/$1', ['as' => 'tarifaAcabadoDelete']); + $routes->post('datatable', 'TarifaAcabados::datatable', ['as' => 'tarifaAcabadoDT']);*/ + + }); + +}); + /* * -------------------------------------------------------------------- diff --git a/ci4/app/Controllers/Js_loader.php b/ci4/app/Controllers/Js_loader.php index 7fabccd0..58b9aa02 100755 --- a/ci4/app/Controllers/Js_loader.php +++ b/ci4/app/Controllers/Js_loader.php @@ -117,5 +117,13 @@ class Js_loader extends BaseController $this->response->setHeader('Content-Type', 'text/javascript'); return view('themes/vuexy/form/presupuestos/cliente/previews.js'); } + + function chat_js() + { + $this->response->setHeader('Content-Type', 'text/javascript'); + return view('themes/vuexy/form/mensajes/mensajeria.js'); + } + + } \ No newline at end of file diff --git a/ci4/app/Controllers/Mensajeria/MensajesDirectos.php b/ci4/app/Controllers/Mensajeria/MensajesDirectos.php new file mode 100644 index 00000000..e951ffc1 --- /dev/null +++ b/ci4/app/Controllers/Mensajeria/MensajesDirectos.php @@ -0,0 +1,80 @@ +viewData['pageTitle'] = "Mensajeria interna"; + + // Breadcrumbs + $this->viewData['breadcrumb'] = [ + ['title' => "Home", 'route' => "javascript:void(0);", 'active' => false], + ['title' => lang("App.menu_mensajes"), 'route' => route_to('mensajeriaView'), 'active' => true] + ]; + + parent::initController($request, $response, $logger); + } + + public function index() + { + + // Modelos + $participantModel = model('App\Models\Mensajeria\ParticipanteModel'); + + + $viewData = [ + 'pageSubTitle' => lang('Basic.global.ManageAllRecords', [lang('Paises.pais')]), + 'conversacionesEntity' => new ConversacionEntity(), + 'usingServerSideDataTable' => true, + ]; + + //$viewData['conversaciones'] = $participantModel->getChatsByUser(auth()->user()->id); + $viewData['conversaciones'] = $participantModel->getChatsByUser(639); + + $viewData = array_merge($this->viewData, $viewData); // merge any possible values from the parent controller class + + return view(static::$viewPath . static::$indexRoute, $viewData); + } + + + public function getChatInfo($conversacionId) + { + // Modelos + $conversacionModel = model('App\Models\Mensajeria\ConversacionModel'); + + // Verificar si es una solicitud AJAX + if ($this->request->isAJAX()) { + // Obtener los datos + $data = [ + 'people' => $conversacionModel->getChatParticipants($conversacionId), + 'messages' => $conversacionModel->getChatMessages($conversacionId) + ]; + + // Devolver respuesta JSON + return $this->respond($data, 200, 'Chat information retrieved successfully'); + } else { + return $this->failForbidden('Only AJAX requests are allowed'); + } + } + + +} diff --git a/ci4/app/Controllers/Test.php b/ci4/app/Controllers/Test.php index 3b6973fc..c8242825 100755 --- a/ci4/app/Controllers/Test.php +++ b/ci4/app/Controllers/Test.php @@ -20,27 +20,30 @@ class Test extends BaseController public function index() { - $model = new PresupuestoModel(); - $data = $model->generarLineaPedido(123); - echo '
';
-        var_dump($data);
-        echo '
'; + // Modelos + $conversacionModel = model('App\Models\Mensajeria\ConversacionModel'); + + + echo "
";
+        echo var_dump($conversacionModel->getChatMessages(190286));
+        echo "
"; } - private function clonar_tarifa_encuadernacion($teOrigen, $teDestino){ + private function clonar_tarifa_encuadernacion($teOrigen, $teDestino) + { $tet_model = model('App\Models\Tarifas\TarifaEncuadernacionTiradaModel'); $tel_model = model('App\Models\Tarifas\TarifaEncuadernacionLineaModel'); - $tarifasTiradas = $tet_model->asObject()->where('tarifa_encuadernacion_id',$teOrigen)->findAll(); + $tarifasTiradas = $tet_model->asObject()->where('tarifa_encuadernacion_id', $teOrigen)->findAll(); - foreach ($tarifasTiradas as $tarifasTirada){ + foreach ($tarifasTiradas as $tarifasTirada) { echo "--->" . $tarifasTirada->id . "
"; - $tarifasLineas = $tel_model->asObject()->where('tirada_encuadernacion_id',$tarifasTirada->id)->findAll(); + $tarifasLineas = $tel_model->asObject()->where('tirada_encuadernacion_id', $tarifasTirada->id)->findAll(); // Prepare the data unset($tarifasTirada->id); @@ -53,7 +56,7 @@ class Test extends BaseController $tet_model->insert($tarifasTirada); $inserted_id = $tet_model->insertID(); - foreach ($tarifasLineas as $tarifasLinea){ + foreach ($tarifasLineas as $tarifasLinea) { echo "------>" . $tarifasLinea->id . "
"; @@ -73,11 +76,10 @@ class Test extends BaseController } - - private function test_get_tirada_alt($tirada, $merma, $tipo_impresion_id, - $json_data, $cliente_id, $ancho, $alto, - $solapas_cubierta, $solapas_ancho_cubierta, $solapas_sobrecubierta, $solapas_ancho_sobrecubierta, $lomo) - { + private function test_get_tirada_alt($tirada, $merma, $tipo_impresion_id, + $json_data, $cliente_id, $ancho, $alto, + $solapas_cubierta, $solapas_ancho_cubierta, $solapas_sobrecubierta, $solapas_ancho_sobrecubierta, $lomo) + { $values = []; if ($json_data) { @@ -160,7 +162,6 @@ class Test extends BaseController } - $opciones_papel = PresupuestoService::get_opciones_papel($uso, $isColor); $datosTipolog = $linea['gotaNegro'] ?? null; @@ -214,7 +215,7 @@ class Test extends BaseController // Previo a ejecutar, vaciar la tabla clientes_precios (ojo si hay customizaciones) - $db = \Config\Database::connect(); + $db = \Config\Database::connect(); $builder = $db->table('cliente_precios'); $plantillaDefectoId = 5; @@ -525,7 +526,6 @@ class Test extends BaseController } - public static function testLineasIntRotativa() { @@ -533,7 +533,6 @@ class Test extends BaseController $tipo = 'negro'; - $datosPedido = (object)array( 'paginas' => 240, 'tirada' => 100, @@ -546,8 +545,8 @@ class Test extends BaseController $parametrosRotativa = (object)array( 'a_favor_fibra' => 0, - 'bnPages' => 240, - 'colorPages' => 0, + 'bnPages' => 240, + 'colorPages' => 0, 'rotativa_gota_negro' => 0, 'rotativa_gota_color' => 0, ); @@ -579,12 +578,12 @@ class Test extends BaseController var_dump($datosTipologias); echo ''; - $parametrosRotativa->rotativa_gota_negro = $datosTipologias[0]->gota_negro; - $parametrosRotativa->rotativa_gota_color = $datosTipologias[0]->gota_color; - $parametrosRotativa->rotativa_negro = $datosTipologias[0]->negro; - $parametrosRotativa->rotativa_cyan = $datosTipologias[0]->cyan; - $parametrosRotativa->rotativa_magenta = $datosTipologias[0]->magenta; - $parametrosRotativa->rotativa_amarillo = $datosTipologias[0]->amarillo; + $parametrosRotativa->rotativa_gota_negro = $datosTipologias[0]->gota_negro; + $parametrosRotativa->rotativa_gota_color = $datosTipologias[0]->gota_color; + $parametrosRotativa->rotativa_negro = $datosTipologias[0]->negro; + $parametrosRotativa->rotativa_cyan = $datosTipologias[0]->cyan; + $parametrosRotativa->rotativa_magenta = $datosTipologias[0]->magenta; + $parametrosRotativa->rotativa_amarillo = $datosTipologias[0]->amarillo; echo '-------------------------------'; $maquinas = $maquina_model->getMaquinaImpresionForPresupuesto( @@ -596,7 +595,6 @@ class Test extends BaseController ); - foreach ($maquinas as $maquina) { echo '----------------------------
'; diff --git a/ci4/app/Models/Mensajeria/ConversacionModel.php b/ci4/app/Models/Mensajeria/ConversacionModel.php index 32aff33d..e6863051 100644 --- a/ci4/app/Models/Mensajeria/ConversacionModel.php +++ b/ci4/app/Models/Mensajeria/ConversacionModel.php @@ -46,6 +46,69 @@ class ConversacionModel extends Model ->get() ->getFirstRow(); } + + + public function getConversacion($convesacionId) + { + $builder = $this->db + ->table($this->table . " t1") + //->select("t1.conversacion_id AS id, t2.asunto AS asunto") + ->where("t1.id", $convesacionId) + ->join("chat_participantes t2", "t2.conversacion_id = t1.id", "left") + ->join("chat_mensajes t3", "t3.conversacion_id = t1.id", "left") + ->orderBy('t1.created_at', 'DESC') + ->get() + ->getResultArray(); + + return $builder; + } + + public function getChatParticipants($chatId) + { + return $this->db + ->table($this->table . " t1") + ->select("t2.usuario_id as user_id, t3.first_name AS nombre, t3.last_name AS apellidos") + ->where("t1.id", $chatId) + ->join("chat_participantes t2", "t2.conversacion_id = t1.id", "left") + ->join("users t3", "t3.id = t2.usuario_id", "left") + ->get() + ->getResultArray(); + } + + public function getChatMessages($chatId) + { + return $this->db + ->table($this->table . " t1") + ->select("t2.mensaje AS mensaje, t2.usuario_id as user_id") + ->select("t3.first_name AS nombre, t3.last_name AS apellidos") + ->where("t1.id", intval($chatId)) + ->join("chat_mensajes t2", "t2.conversacion_id = t1.id", "left") + ->join("users t3", "t3.id = t2.usuario_id", "left") + ->get() + ->getResultArray(); + } + + + + + + + + + + + + + + + + + + + + + + /** * Devuelve si la conversacion tiene algún cliente diff --git a/ci4/app/Models/Mensajeria/ParticipanteModel.php b/ci4/app/Models/Mensajeria/ParticipanteModel.php index f20c177e..7a0e0a9c 100644 --- a/ci4/app/Models/Mensajeria/ParticipanteModel.php +++ b/ci4/app/Models/Mensajeria/ParticipanteModel.php @@ -20,7 +20,29 @@ class ParticipanteModel extends BaseModel 'last_read', ]; - protected $useTimestamps = true; + public function getPeopleInChat($conversacionId){ + + $builder = $this->db + ->table($this->table . " t1") + ->where("t1.conversacion_id", $conversacionId); + + return $builder; + } + + public function getChatsByUser($userId){ + + $builder = $this->db + ->table($this->table . " t1") + ->select("t1.conversacion_id AS id, t2.asunto AS asunto") + ->where("t1.usuario_id", $userId) + ->join("chat_conversaciones t2", "t2.id = conversacion_id", "left") + ->orderBy('t1.created_at', 'DESC') + ->get() + ->getResultObject(); + + return $builder; + } + public function getCustomer() { diff --git a/ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js b/ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js new file mode 100644 index 00000000..89395845 --- /dev/null +++ b/ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js @@ -0,0 +1,138 @@ +/** + * App Chat + */ + +// Seleccionar elementos del DOM +const chatContactsBody = $('.app-chat-contacts .sidebar-body'), + chatContactListItems = $('.chat-contact-list-item:not(.chat-contact-list-item-title)'), + chatHistoryBody = $('.chat-history-body'), + chatSidebarLeftUserAbout = $('.chat-sidebar-left-user-about'), + messageInput = $('.message-input'), + searchInput = $('.chat-search-input'), + sendMsgBtn = $('.send-msg-btn'); // Seleccionar el botón de envío de mensaje + +// Inicializar PerfectScrollbar +if (chatContactsBody.length) { + new PerfectScrollbar(chatContactsBody[0], { + wheelPropagation: false, + suppressScrollX: true + }); +} + +if (chatHistoryBody.length) { + new PerfectScrollbar(chatHistoryBody[0], { + wheelPropagation: false, + suppressScrollX: true + }); +} + +// Función para desplazar el scroll al final +function scrollToBottom() { + if (chatHistoryBody.length) { + chatHistoryBody.scrollTop(chatHistoryBody[0].scrollHeight); + } +} + +scrollToBottom(); + +// Seleccionar chat o contacto +chatContactListItems.on('click', function () { + chatContactListItems.removeClass('active'); + $(this).addClass('active'); +}); + +// Filtrar chats +if (searchInput.length) { + searchInput.on('keyup', function () { + const searchValue = $(this).val().toLowerCase(), + chatListItem0 = $('.chat-list-item-0'), + contactListItem0 = $('.contact-list-item-0'), + searchChatListItems = $('#chat-list li:not(.chat-contact-list-item-title)'), + searchContactListItems = $('#contact-list li:not(.chat-contact-list-item-title)'); + + // Buscar en chats + const chatListItemsCount = searchChatContacts(searchChatListItems, searchValue); + // Mostrar u ocultar mensaje de "No se encontraron resultados" en chats + if (chatListItem0.length) { + chatListItem0.toggleClass('d-none', chatListItemsCount === 0); + } + + // Buscar en contactos + const contactListItemsCount = searchChatContacts(searchContactListItems, searchValue); + // Mostrar u ocultar mensaje de "No se encontraron resultados" en contactos + if (contactListItem0.length) { + contactListItem0.toggleClass('d-none', contactListItemsCount === 0); + } + }); +} + +// Función para buscar en chats y contactos +function searchChatContacts(searchListItems, searchValue) { + let searchListItemsCount = 0; + searchListItems.each(function () { + const searchListItemText = $(this).text().toLowerCase(); + const matchesSearch = searchListItemText.indexOf(searchValue) !== -1; + + $(this).toggleClass('d-flex', matchesSearch); + $(this).toggleClass('d-none', !matchesSearch); + + if (matchesSearch) { + searchListItemsCount++; + } + }); + + return searchListItemsCount; +} + +// Enviar mensaje +if (sendMsgBtn.length) { + sendMsgBtn.on('click', function (e) { + e.preventDefault(); + if (messageInput.val()) { + const renderMsg = $('
').addClass('chat-message-text mt-2').html(`

${messageInput.val()}

`); + const lastChatMessageWrapper = $('li:last-child .chat-message-wrapper'); + if (lastChatMessageWrapper.length) { + lastChatMessageWrapper.append(renderMsg); + } + messageInput.val(''); + scrollToBottom(); + } + }); +} + +// Seleccionar los elementos

con la clase .chat-contact-status +$('.chat-contact-status').on('click', function () { + // Obtener el id de la conversación desde el atributo id del elemento

+ var conversationId = $(this).attr('id'); + + console.log(conversationId) + + // Realizar la llamada AJAX + $.ajax({ + url: 'internos/chat/' + conversationId, // Cambia esta URL por la ruta correcta a tu API + type: 'GET', + dataType: 'json', + success: function (data) { + // Manejar la respuesta exitosa de la llamada AJAX + console.log('Datos recibidos:', data); + // Aquí puedes actualizar el DOM o realizar otras acciones con los datos recibidos + if (Array.isArray(data.people) && data.people.length > 0) { + // Limpiar el contenedor donde se mostrarán los participantes + $('#participants-container').empty(); + data.people.forEach(person => { + // Crear el HTML para cada participante y agregarlo al contenedor + var participantHtml = ` +

+ ${person.user_id} +
`; + $('#participants-container').append(participantHtml); + }); + } + }, + error: function (jqXHR, textStatus, errorThrown) { + // Manejar errores en la llamada AJAX + console.error('Error en la llamada AJAX:', textStatus, errorThrown); + } + }); +}) +; \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php b/ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php new file mode 100644 index 00000000..8d330963 --- /dev/null +++ b/ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php @@ -0,0 +1,364 @@ +include("themes/_commonPartialsBs/select2bs5") ?> +include("themes/_commonPartialsBs/datatables") ?> +extend('themes/vuexy/main/defaultlayout') ?> + +section('content'); ?> + +
+
+
+ +
+ +
+ +
+ + + +
+
+
+
+
+
+ P +
+
+ + +
+
+
+
    +
  • +
    +
    +
    +

    How can we help? We're here for you! 😄

    +
    +
    + + 10:00 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    Hey John, I am looking for the best admin template.

    +

    Could you please help me to find it out? 🤔

    +
    +
    +

    It should be Bootstrap 5 compatible.

    +
    +
    + 10:02 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    Vuexy has all the components you'll ever need in a + app.

    +
    +
    + + 10:03 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    Looks clean and fresh UI. 😃

    +
    +
    +

    It's perfect for my next project.

    +
    +
    +

    How can I purchase it?

    +
    +
    + 10:05 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    Thanks, you can purchase it.

    +
    +
    + + 10:06 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    I will purchase it for sure. 👍

    +
    +
    +

    Thanks.

    +
    +
    + 10:08 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    Great, Feel free to get in touch.

    +
    +
    + + 10:10 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
  • +
    +
    +
    + Avatar +
    +
    +
    +
    +

    Do you have design files for Vuexy?

    +
    +
    + 10:15 AM +
    +
    +
    +
  • +
  • +
    +
    +
    +

    + Yes that's correct documentation file, Design files are included + with + the template. +

    +
    +
    + + 10:15 AM +
    +
    +
    +
    + Avatar +
    +
    +
    +
  • +
+
+ + +
+
+ +
+
+
+endSection() ?> + +section('additionalInlineJs') ?> + + + + +endSection() ?> + + + +section('css') ?> + +endSection() ?> + + +section('additionalExternalJs') ?> + + +endSection() ?> diff --git a/ci4/app/Views/themes/vuexy/main/menus/mensajes_menu.php b/ci4/app/Views/themes/vuexy/main/menus/mensajes_menu.php index 051999dc..44913967 100644 --- a/ci4/app/Views/themes/vuexy/main/menus/mensajes_menu.php +++ b/ci4/app/Views/themes/vuexy/main/menus/mensajes_menu.php @@ -6,7 +6,7 @@ if (auth()->user()->inGroup('beta')) { ?>
+ + +section('css') ?> + +endSection() ?> + + + +section("additionalExternalJs") ?> + + + +endSection() ?> \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/components/chat_presupuesto.php b/ci4/app/Views/themes/vuexy/components/chat_presupuesto.php new file mode 100644 index 00000000..78e97c68 --- /dev/null +++ b/ci4/app/Views/themes/vuexy/components/chat_presupuesto.php @@ -0,0 +1,156 @@ +
+
+

+ +

+
+
+
+
+
+ + +
+ +
+ +
+ + + +
+
+
+
+
+ +
+ P +
+
+
Departamento Producción
+ Consulta sobre el presupuesto P001 +
+
+ +
+
+
+
    + + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+ +section('css') ?> + +endSection() ?> + + + +section("additionalExternalJs") ?> + + + +endSection() ?> \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js b/ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js index 89395845..4e7667b8 100644 --- a/ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js +++ b/ci4/app/Views/themes/vuexy/form/mensajes/mensajeria.js @@ -86,53 +86,53 @@ function searchChatContacts(searchListItems, searchValue) { // Enviar mensaje if (sendMsgBtn.length) { - sendMsgBtn.on('click', function (e) { - e.preventDefault(); - if (messageInput.val()) { - const renderMsg = $('
').addClass('chat-message-text mt-2').html(`

${messageInput.val()}

`); - const lastChatMessageWrapper = $('li:last-child .chat-message-wrapper'); - if (lastChatMessageWrapper.length) { - lastChatMessageWrapper.append(renderMsg); - } - messageInput.val(''); - scrollToBottom(); - } - }); + // sendMsgBtn.on('click', function (e) { + // e.preventDefault(); + // if (messageInput.val()) { + // const renderMsg = $('
').addClass('chat-message-text mt-2').html(`

${messageInput.val()}

`); + // const lastChatMessageWrapper = $('li:last-child .chat-message-wrapper'); + // if (lastChatMessageWrapper.length) { + // lastChatMessageWrapper.append(renderMsg); + // } + // messageInput.val(''); + // scrollToBottom(); + // } + // }); } -// Seleccionar los elementos

con la clase .chat-contact-status -$('.chat-contact-status').on('click', function () { - // Obtener el id de la conversación desde el atributo id del elemento

- var conversationId = $(this).attr('id'); +// // Seleccionar los elementos

con la clase .chat-contact-status +// $('.chat-contact-status').on('click', function () { +// // Obtener el id de la conversación desde el atributo id del elemento

+// var conversationId = $(this).attr('id'); - console.log(conversationId) +// console.log(conversationId) - // Realizar la llamada AJAX - $.ajax({ - url: 'internos/chat/' + conversationId, // Cambia esta URL por la ruta correcta a tu API - type: 'GET', - dataType: 'json', - success: function (data) { - // Manejar la respuesta exitosa de la llamada AJAX - console.log('Datos recibidos:', data); - // Aquí puedes actualizar el DOM o realizar otras acciones con los datos recibidos - if (Array.isArray(data.people) && data.people.length > 0) { - // Limpiar el contenedor donde se mostrarán los participantes - $('#participants-container').empty(); - data.people.forEach(person => { - // Crear el HTML para cada participante y agregarlo al contenedor - var participantHtml = ` -

- ${person.user_id} -
`; - $('#participants-container').append(participantHtml); - }); - } - }, - error: function (jqXHR, textStatus, errorThrown) { - // Manejar errores en la llamada AJAX - console.error('Error en la llamada AJAX:', textStatus, errorThrown); - } - }); -}) -; \ No newline at end of file +// // Realizar la llamada AJAX +// $.ajax({ +// url: 'internos/chat/' + conversationId, // Cambia esta URL por la ruta correcta a tu API +// type: 'GET', +// dataType: 'json', +// success: function (data) { +// // Manejar la respuesta exitosa de la llamada AJAX +// console.log('Datos recibidos:', data); +// // Aquí puedes actualizar el DOM o realizar otras acciones con los datos recibidos +// if (Array.isArray(data.people) && data.people.length > 0) { +// // Limpiar el contenedor donde se mostrarán los participantes +// $('#participants-container').empty(); +// data.people.forEach(person => { +// // Crear el HTML para cada participante y agregarlo al contenedor +// var participantHtml = ` +//
+// ${person.user_id} +//
`; +// $('#participants-container').append(participantHtml); +// }); +// } +// }, +// error: function (jqXHR, textStatus, errorThrown) { +// // Manejar errores en la llamada AJAX +// console.error('Error en la llamada AJAX:', textStatus, errorThrown); +// } +// }); +// }) +// ; \ No newline at end of file diff --git a/ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php b/ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php index 8d330963..77c7cbe1 100644 --- a/ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php +++ b/ci4/app/Views/themes/vuexy/form/mensajes/mensajesView.php @@ -5,360 +5,12 @@ section('content'); ?>
-
-
- -
- -
- -
- - - -
-
-
-
-
-
- P -
-
- - -
-
-
-
    -
  • -
    -
    -
    -

    How can we help? We're here for you! 😄

    -
    -
    - - 10:00 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    Hey John, I am looking for the best admin template.

    -

    Could you please help me to find it out? 🤔

    -
    -
    -

    It should be Bootstrap 5 compatible.

    -
    -
    - 10:02 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    Vuexy has all the components you'll ever need in a - app.

    -
    -
    - - 10:03 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    Looks clean and fresh UI. 😃

    -
    -
    -

    It's perfect for my next project.

    -
    -
    -

    How can I purchase it?

    -
    -
    - 10:05 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    Thanks, you can purchase it.

    -
    -
    - - 10:06 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    I will purchase it for sure. 👍

    -
    -
    -

    Thanks.

    -
    -
    - 10:08 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    Great, Feel free to get in touch.

    -
    -
    - - 10:10 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    Do you have design files for Vuexy?

    -
    -
    - 10:15 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    - Yes that's correct documentation file, Design files are included - with - the template. -

    -
    -
    - - 10:15 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
-
- - -
-
- -
-
+ null]) ?>
endSection() ?> -section('additionalInlineJs') ?> -endSection() ?> - - -section('css') ?> - -endSection() ?> - - -section('additionalExternalJs') ?> - - -endSection() ?> diff --git a/ci4/app/Views/themes/vuexy/form/pedidos/viewPedidoForm.php b/ci4/app/Views/themes/vuexy/form/pedidos/viewPedidoForm.php index fc16989d..78102e6e 100644 --- a/ci4/app/Views/themes/vuexy/form/pedidos/viewPedidoForm.php +++ b/ci4/app/Views/themes/vuexy/form/pedidos/viewPedidoForm.php @@ -19,8 +19,9 @@ user()->inGroup('cliente-admin') || auth()->user()->inGroup('cliente-editor'))) : ?> - - + + + $pedidoEntity->id]) ?>
"btn btn-secondary float-start"]) ?> @@ -30,6 +31,7 @@
+ endSection() ?> diff --git a/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php index 86abbe58..0e57e675 100644 --- a/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php +++ b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/_mensajeria.php @@ -1,4 +1,4 @@ -
+
@@ -33,56 +33,14 @@
@@ -121,7 +69,7 @@
-
+
@@ -157,191 +105,10 @@
-
-
    -
  • -
    -
    -
    -

    How can we help? We're here for you! 😄

    -
    -
    - - 10:00 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    Hey John, I am looking for the best admin template.

    -

    Could you please help me to find it out? 🤔

    -
    -
    -

    It should be Bootstrap 5 compatible.

    -
    -
    - 10:02 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    Vuexy has all the components you'll ever need in a app.

    -
    -
    - - 10:03 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    Looks clean and fresh UI. 😃

    -
    -
    -

    It's perfect for my next project.

    -
    -
    -

    How can I purchase it?

    -
    -
    - 10:05 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    Thanks, you can purchase it.

    -
    -
    - - 10:06 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    I will purchase it for sure. 👍

    -
    -
    -

    Thanks.

    -
    -
    - 10:08 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    Great, Feel free to get in touch.

    -
    -
    - - 10:10 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • -
  • -
    -
    -
    - Avatar -
    -
    -
    -
    -

    Do you have design files for Vuexy?

    -
    -
    - 10:15 AM -
    -
    -
    -
  • -
  • -
    -
    -
    -

    - Yes that's correct documentation file, Design files are included with - the template. -

    -
    -
    - - 10:15 AM -
    -
    -
    -
    - Avatar -
    -
    -
    -
  • +
    +
      + +
    @@ -352,9 +119,10 @@ placeholder="Type your message here" />
@@ -368,131 +136,17 @@
- +section('css') ?> + +endSection() ?> -section("additionalInlineJs") ?> - -/** - * App Chat - */ - -// Seleccionar elementos del DOM -const chatContactsBody = document.querySelector('.app-chat-contacts .sidebar-body'), - chatContactListItems = [].slice.call( - document.querySelectorAll('.chat-contact-list-item:not(.chat-contact-list-item-title)') - ), - chatHistoryBody = document.querySelector('.chat-history-body'), - chatSidebarLeftUserAbout = document.querySelector('.chat-sidebar-left-user-about'), - messageInput = document.querySelector('.message-input'), - searchInput = document.querySelector('.chat-search-input'), - sendMsgBtn = document.querySelector('.send-msg-btn'); // Seleccionar el botón de envío de mensaje - -// Inicializar PerfectScrollbar -if (chatContactsBody) { - new PerfectScrollbar(chatContactsBody, { - wheelPropagation: false, - suppressScrollX: true - }); -} - -if (chatHistoryBody) { - new PerfectScrollbar(chatHistoryBody, { - wheelPropagation: false, - suppressScrollX: true - }); -} - -// Función para desplazar el scroll al final -function scrollToBottom() { - if (chatHistoryBody) { - chatHistoryBody.scrollTo(0, chatHistoryBody.scrollHeight); - } -} -scrollToBottom(); - -// Seleccionar chat o contacto -chatContactListItems.forEach(chatContactListItem => { - chatContactListItem.addEventListener('click', e => { - chatContactListItems.forEach(item => { - item.classList.remove('active'); - }); - e.currentTarget.classList.add('active'); - }); -}); - -// Filtrar chats -if (searchInput) { - searchInput.addEventListener('keyup', e => { - const searchValue = e.currentTarget.value.toLowerCase(), - chatListItem0 = document.querySelector('.chat-list-item-0'), - contactListItem0 = document.querySelector('.contact-list-item-0'), - searchChatListItems = [].slice.call( - document.querySelectorAll('#chat-list li:not(.chat-contact-list-item-title)') - ), - searchContactListItems = [].slice.call( - document.querySelectorAll('#contact-list li:not(.chat-contact-list-item-title)') - ); - - // Buscar en chats - const chatListItemsCount = searchChatContacts(searchChatListItems, searchValue); - // Mostrar u ocultar mensaje de "No se encontraron resultados" en chats - if (chatListItem0) { - chatListItem0.classList.toggle('d-none', chatListItemsCount !== 0); - } - - // Buscar en contactos - const contactListItemsCount = searchChatContacts(searchContactListItems, searchValue); - // Mostrar u ocultar mensaje de "No se encontraron resultados" en contactos - if (contactListItem0) { - contactListItem0.classList.toggle('d-none', contactListItemsCount !== 0); - } - }); -} - -// Función para buscar en chats y contactos -function searchChatContacts(searchListItems, searchValue) { - let searchListItemsCount = 0; - searchListItems.forEach(searchListItem => { - const searchListItemText = searchListItem.textContent.toLowerCase(); - const matchesSearch = searchListItemText.indexOf(searchValue) !== -1; - - searchListItem.classList.toggle('d-flex', matchesSearch); - searchListItem.classList.toggle('d-none', !matchesSearch); - - if (matchesSearch) { - searchListItemsCount++; - } - }); - - return searchListItemsCount; -} - -// Enviar mensaje -if (sendMsgBtn) { - sendMsgBtn.addEventListener('click', e => { - e.preventDefault(); - if (messageInput.value) { - const renderMsg = document.createElement('div'); - renderMsg.className = 'chat-message-text mt-2'; - renderMsg.innerHTML = `

${messageInput.value}

`; - const lastChatMessageWrapper = document.querySelector('li:last-child .chat-message-wrapper'); - if (lastChatMessageWrapper) { - lastChatMessageWrapper.appendChild(renderMsg); - } - messageInput.value = ''; - scrollToBottom(); - } - }); -} - - - - - - - +section("additionalExternalJs") ?> + + + endSection() ?> + diff --git a/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php index 5d3c296c..638cc728 100644 --- a/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php +++ b/ci4/app/Views/themes/vuexy/form/presupuestos/cosidotapablanda/viewCosidotapablandaForm.php @@ -34,9 +34,9 @@ + $presupuestoId]) ?> - diff --git a/httpdocs/assets/js/safekat/components/ajax.js b/httpdocs/assets/js/safekat/components/ajax.js new file mode 100644 index 00000000..9045d117 --- /dev/null +++ b/httpdocs/assets/js/safekat/components/ajax.js @@ -0,0 +1,46 @@ +class Ajax{ + constructor(url, data, headers, success, error, type='default'){ + this.url = url; + this.data = data; + this.headers = headers; + this.success = success; + this.error = error; + this.type = type; + } + post(){ + (this.type == 'default') ? this.ajax('POST'): this.ajaxForm('POST'); + } + get(){ + this.ajax('GET'); + } + put(){ + (this.type == 'default') ? this.ajax('PUT'): this.ajaxForm('PUT'); + } + delete(){ + (this.type == 'default') ? this.ajax('DELETE'): this.ajaxForm('DELETE'); + } + ajax(method){ + $.ajax({ + url : this.url, + type : method, + data: this.data, + headers: this.headers, + success: this.success, + error: this.error + }) + } + ajaxForm(method){ + $.ajax({ + url : this.url, + type : method, + data: this.data, + processData: false, + contentType: false, + headers: this.headers, + success: this.success, + error: this.error + }) + } +} + +export default Ajax \ No newline at end of file diff --git a/httpdocs/assets/js/safekat/components/chat.js b/httpdocs/assets/js/safekat/components/chat.js new file mode 100644 index 00000000..67136447 --- /dev/null +++ b/httpdocs/assets/js/safekat/components/chat.js @@ -0,0 +1,446 @@ +import Ajax from '../components/ajax.js' + + + +class Chat { + constructor(domItem) { + this.domItem = domItem + this.chatList = this.domItem.find("#chat-list") + this.chatHistory = this.domItem.find("#chat-conversation") + this.modelId = this.domItem.data("id") + this.chatHistoryBody = document.querySelector(".chat-history-body") + this.sendBtnMessageDepartment = this.domItem.find("#send-msg-btn-deparment") + this.sendBtnMessageInternal= this.domItem.find("#send-msg-btn-internal") + + this.messageInput = this.domItem.find(".message-input") + this.sideBar = this.domItem.find(".sidebar-body") + this.chatDeparmentId = undefined + + this.headers = {} + + this.chatContactsBody = document.querySelector('.app-chat-contacts .sidebar-body') + this.chatContactListItems = [].slice.call( + document.querySelectorAll('.chat-contact-list-item:not(.chat-contact-list-item-title)') + ) + } + init() { + // Inicializar PerfectScrollbar + this.sendBtnMessageDepartment.addClass("d-none") + this.sendBtnMessageInternal.addClass("d-none") + if (this.chatContactsBody) { + this.scrollbarContacts = new PerfectScrollbar(this.chatContactsBody, { + wheelPropagation: false, + suppressScrollX: true + }); + } + + if (this.chatHistoryBody) { + this.scrollbarChatHistory = new PerfectScrollbar(this.chatHistoryBody, { + wheelPropagation: false, + suppressScrollX: true + }); + } + } + initGeneral() { + this.chatType = "general" + this._handleGetChatList() + this.sendBtnMessageDepartment.on("click", this._sendMessage.bind(this)) + + } + initPresupuesto() { + this.chatType = "presupuesto" + this._handleGetChatList() + this.sendBtnMessageDepartment.on("click", this._sendMessage.bind(this)) + + } + initPedido() { + this.chatType = "pedido" + this._handleGetChatList() + this.sendBtnMessageDepartment.on("click", this._sendMessage.bind(this)) + + } + initFactura() { + this.chatType = "factura" + this._handleGetChatList() + this.sendBtnMessageDepartment.on("click", this._sendMessage.bind(this)) + + } + initContacts() { + this.chatType = "internal" + + } + _setBtnInternal() + { + this.sendBtnMessageInternal.removeClass("d-none") + this.sendBtnMessageDepartment.addClass("d-none") + } + _setBtnDeparment() + { + this.sendBtnMessageDepartment.removeClass("d-none") + this.sendBtnMessageInternal.addClass("d-none") + } + /**============================================ + * PRESUPUESTOS + *=============================================**/ + _handleGetChatList(event) { + let ajax = new Ajax( + "/chat/departments", + null, + null, + this._handleGetChatListSuccess.bind(this), + this._handleGetChatListError.bind(this), + + + ); + ajax.get(); + } + _handleGetChatListSuccess(data) { + Object.values(data).map(row => { + this.chatList.append(this._getContact(row)) + this.chatList.find(`#chat_${row.name}`).on("click", (event) => { + let chatDeparmentId = this.chatList.find(`#chat_${row.name}`).data("id") + this.domItem.find(".chat-history-header div.chat-contact-info h6").text(row.display) + this.domItem.find(".chat-history-header div.chat-contact-info small.user-status").text(row.display) + this._getChatMessage(chatDeparmentId) + }) + + }) + this.chatList.find(`#chat__produccion`).trigger("click"); + } + _handleGetChatListError(error) { + console.error(error) + } + _getContact(row) { + let chat = ` +
  • + +
    + ${row.display.charAt(0)} +
    +
    +
    ${row.display}
    +

    + ${row.display} +

    +
    +
    +
  • + ` + return chat + } + _getChatMessage(chatDeparmentId) { + this.chatDeparmentId = chatDeparmentId + let ajax = new Ajax( + `/chat/department/${this.chatType}/${this.chatDeparmentId}/${this.modelId}`, + null, + null, + this._getChatMessageSuccess.bind(this), + this._getChatMessageError.bind(this), + + + ); + ajax.get(); + } + _getChatMessageSuccess(data) { + this.chatHistory.empty() + this._setBtnDeparment() + if (data.messages) { + data.messages.map((m) => { + this._addChatMessage(m) + }) + } + } + _getChatMessageError(err) { + console.log(err) + + } + _addChatMessage(chatMessage, pos = "left") { + let chatItem = ` +
  • +
    +
    + +
    +

    ${chatMessage?.message}

    +
    +
    +
    + + ${chatMessage?.user?.first_name + " " + chatMessage?.user?.last_name} +
    + + ${chatMessage.created_at} +
    + +
    +
    +
    + ${chatMessage?.user?.first_name.charAt(0) + chatMessage?.user?.last_name.charAt(0)} +
    +
    +
    +
  • + ` + this.chatHistory.append(chatItem) + return chatItem + } + _sendMessage() { + let messageText = this.messageInput.val() + const body = { + message: messageText, + chat_department_id: this.chatDeparmentId, + user: this.userId, + model_id: this.modelId + } + if (messageText) { + let ajax = new Ajax( + `/chat/message/${this.chatType}`, + body, + null, + this._sendMessageSuccess.bind(this), + this._sendMessageError.bind(this), + + ); + ajax.post(); + } + } + _sendMessageSuccess(data) { + this.messageInput.val("") + this._getChatMessage(this.chatDeparmentId) + } + _sendMessageError(err) { + console.error(err) + } + _handleListContacts() { + this.sideBar.find("#contact-list").removeClass("d-none") + this.sendBtnMessageInternal.on("click",this._sendMessageInternal.bind(this)) + let ajax = new Ajax( + "/chat/contacts", + null, + null, + this._handleListContactsSuccess.bind(this), + this._handleListContactsError.bind(this) + + ) + ajax.get() + + } + _handleListContactsSuccess(contacts) { + if (contacts) { + contacts.map((c) => { + this._addContactToList(c) + }); + } else { + this.sideBar.find("#contact-list").removeClass("d-none") + } + this.sideBar.find(".contact-chat").on("click", (e) => { + let userId = $(e.currentTarget).data("id") + this.receiverId = userId + this._handleGetSingleContact(userId) + this._setBtnInternal() + this.chatHistory.empty() + }) + } + _handleListContactsError(err) { + console.error(err) + } + _handleGetSingleContact(userId) { + let ajax = new Ajax( + `/chat/contacts/${userId}`, + null, + null, + this._handleGetSingleContactSuccess.bind(this), + this._handleGetSingleContactError.bind(this), + ) + ajax.get() + } + + _handleGetSingleContactSuccess(contact) { + this.domItem.find(".chat-history-header div.chat-contact-info h6").text([contact.first_name, contact.last_name].join(" ")) + this.domItem.find(".chat-history-header div.chat-contact-info small.user-status").text(contact.username) + let ajax = new Ajax( + `/chat/contact/${contact.id}/messages`, + null, + null, + this._handleGetSingleContactMessagesSuccess.bind(this), + this._handleGetSingleContactMessagesError.bind(this) + ) + ajax.get() + } + _handleGetSingleContactMessagesSuccess(data) { + if (data) { + data.map((m) => { + this._addChatMessage(m) + }) + } + } + _handleGetSingleContactMessagesError(err) { } + + _handleGetSingleContactError(err) { + + } + _sendMessageInternal() { + let messageText = this.messageInput.val() + const body = { + message: messageText, + receiver_id: this.receiverId, + } + if (messageText) { + let ajax = new Ajax( + `/chat/message/internal`, + body, + null, + this._sendMessageInternalSuccess.bind(this), + this._sendMessageInternalError.bind(this), + + ); + ajax.post(); + } + } + _sendMessageInternalSuccess(message) { + this.messageInput.val("") + this._handleGetSingleContact(this.receiverId) + } + _sendMessageInternalError(err) { + console.error(err) + } + _addContactToList(contact) { + + let contactItem = + ` +
  • + +
    + + ${contact?.first_name?.charAt(0) ?? "?" + + contact?.last_name?.charAt(0) ?? "?"} + +
    +
    +
    ${contact?.first_name ?? "" + " " + + contact?.last_name ?? ""}
    +

    + ${contact.username} +

    +
    + ${contact.unreadMessages ? `${contact.unreadMessages}` : ""} +
    +
  • + ` + + if (contact.first_name || contact.last_name) { + this.sideBar.find("#contact-list").append(contactItem) + } + } + + + + + + +} +export default Chat + + +// // Seleccionar elementos del DOM +// const chatContactsBody = document.querySelector('.app-chat-contacts .sidebar-body'), +// chatContactListItems = [].slice.call( +// document.querySelectorAll('.chat-contact-list-item:not(.chat-contact-list-item-title)') +// ), +// chatHistoryBody = document.querySelector('.chat-history-body'), +// chatSidebarLeftUserAbout = document.querySelector('.chat-sidebar-left-user-about'), +// messageInput = document.querySelector('.message-input'), +// searchInput = document.querySelector('.chat-search-input'), +// sendMsgBtn = document.querySelector('.send-msg-btn'); // Seleccionar el botón de envío de mensaje + +// // Inicializar PerfectScrollbar +// if (chatContactsBody) { +// new PerfectScrollbar(chatContactsBody, { +// wheelPropagation: false, +// suppressScrollX: true +// }); +// } + +// if (chatHistoryBody) { +// new PerfectScrollbar(chatHistoryBody, { +// wheelPropagation: false, +// suppressScrollX: true +// }); +// } + +// // Función para desplazar el scroll al final +// function scrollToBottom() { +// if (chatHistoryBody) { +// chatHistoryBody.scrollTo(0, chatHistoryBody.scrollHeight); +// } +// } +// scrollToBottom(); + +// // Seleccionar chat o contacto + + +// // Filtrar chats +// if (searchInput) { +// searchInput.addEventListener('keyup', e => { +// const searchValue = e.currentTarget.value.toLowerCase(), +// chatListItem0 = document.querySelector('.chat-list-item-0'), +// contactListItem0 = document.querySelector('.contact-list-item-0'), +// searchChatListItems = [].slice.call( +// document.querySelectorAll('#chat-list li:not(.chat-contact-list-item-title)') +// ), +// searchContactListItems = [].slice.call( +// document.querySelectorAll('#contact-list li:not(.chat-contact-list-item-title)') +// ); + +// // Buscar en chats +// const chatListItemsCount = searchChatContacts(searchChatListItems, searchValue); +// // Mostrar u ocultar mensaje de "No se encontraron resultados" en chats +// if (chatListItem0) { +// chatListItem0.classList.toggle('d-none', chatListItemsCount !== 0); +// } + +// // Buscar en contactos +// const contactListItemsCount = searchChatContacts(searchContactListItems, searchValue); +// // Mostrar u ocultar mensaje de "No se encontraron resultados" en contactos +// if (contactListItem0) { +// contactListItem0.classList.toggle('d-none', contactListItemsCount !== 0); +// } +// }); +// } + +// // Función para buscar en chats y contactos +// function searchChatContacts(searchListItems, searchValue) { +// let searchListItemsCount = 0; +// searchListItems.forEach(searchListItem => { +// const searchListItemText = searchListItem.textContent.toLowerCase(); +// const matchesSearch = searchListItemText.indexOf(searchValue) !== -1; + +// searchListItem.classList.toggle('d-flex', matchesSearch); +// searchListItem.classList.toggle('d-none', !matchesSearch); + +// if (matchesSearch) { +// searchListItemsCount++; +// } +// }); + +// return searchListItemsCount; +// } + +// // // Enviar mensaje +// // if (sendMsgBtn) { +// // sendMsgBtn.addEventListener('click', e => { +// // e.preventDefault(); +// // if (messageInput.value) { +// // const renderMsg = document.createElement('div'); +// // renderMsg.className = 'chat-message-text mt-2'; +// // renderMsg.innerHTML = `

    ${messageInput.value}

    `; +// // const lastChatMessageWrapper = document.querySelector('li:last-child .chat-message-wrapper'); +// // if (lastChatMessageWrapper) { +// // lastChatMessageWrapper.appendChild(renderMsg); +// // } +// // messageInput.value = ''; +// // scrollToBottom(); +// // } +// // }); +// // } diff --git a/httpdocs/assets/js/safekat/pages/chatFactura.js b/httpdocs/assets/js/safekat/pages/chatFactura.js new file mode 100644 index 00000000..76102699 --- /dev/null +++ b/httpdocs/assets/js/safekat/pages/chatFactura.js @@ -0,0 +1,4 @@ +import Chat from '../components/chat.js' + +let chat = new Chat($("#chat-factura")) +chat.initFactura() \ No newline at end of file diff --git a/httpdocs/assets/js/safekat/pages/chatPedido.js b/httpdocs/assets/js/safekat/pages/chatPedido.js new file mode 100644 index 00000000..90e25c42 --- /dev/null +++ b/httpdocs/assets/js/safekat/pages/chatPedido.js @@ -0,0 +1,5 @@ +import Chat from '../components/chat.js' + +let chat = new Chat($("#chat-pedido")) +chat.init() +chat.initPedido() \ No newline at end of file diff --git a/httpdocs/assets/js/safekat/pages/chatPresupuesto.js b/httpdocs/assets/js/safekat/pages/chatPresupuesto.js new file mode 100644 index 00000000..4a09c66d --- /dev/null +++ b/httpdocs/assets/js/safekat/pages/chatPresupuesto.js @@ -0,0 +1,6 @@ +import Chat from '../components/chat.js' + +let chat = new Chat($("#chat-presupuesto")) +chat.init() +chat.initPresupuesto() +chat._handleListContacts() \ No newline at end of file diff --git a/httpdocs/themes/vuexy/img/avatars/user.png b/httpdocs/themes/vuexy/img/avatars/user.png new file mode 100644 index 0000000000000000000000000000000000000000..2b8b658355da59697e17ce72946d1f9bb1642631 GIT binary patch literal 19293 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 literal 0 HcmV?d00001 diff --git a/httpdocs/themes/vuexy/vendor/css/pages/app-chat.css b/httpdocs/themes/vuexy/vendor/css/pages/app-chat.css index 5fa07556..647e1e2e 100755 --- a/httpdocs/themes/vuexy/vendor/css/pages/app-chat.css +++ b/httpdocs/themes/vuexy/vendor/css/pages/app-chat.css @@ -57,6 +57,7 @@ height: calc(calc(100vh - 11.5rem) - 5rem - 2.2rem); } } + .app-chat .app-chat-contacts .sidebar-body .chat-contact-list li.chat-contact-list-item { display: flex; justify-content: space-between; From d6a95f78ee91216d363e3c2659b3536ecb0ec359 Mon Sep 17 00:00:00 2001 From: Alvaro Mazuecos Nogales Date: Mon, 23 Sep 2024 10:00:47 +0200 Subject: [PATCH 10/10] feat : add chat in facturas,presupuestos y pedidos --- .../themes/vuexy/components/chat_factura.php | 94 +++---- .../themes/vuexy/components/chat_general.php | 152 +++++++++++ .../themes/vuexy/components/chat_pedido.php | 242 +++++++++--------- .../vuexy/components/chat_presupuesto.php | 67 +++-- .../vuexy/form/facturas/viewFacturaForm.php | 1 + .../vuexy/form/mensajes/mensajesView.php | 10 +- .../vuexy/form/pedidos/viewPedidoForm.php | 67 ++--- .../assets/js/safekat/pages/chatFactura.js | 4 +- .../assets/js/safekat/pages/chatGeneral.js | 5 + .../assets/js/safekat/pages/chatPedido.js | 3 +- .../vuexy/vendor/css/pages/app-chat.css | 3 + 11 files changed, 394 insertions(+), 254 deletions(-) create mode 100644 httpdocs/assets/js/safekat/pages/chatGeneral.js diff --git a/ci4/app/Views/themes/vuexy/components/chat_factura.php b/ci4/app/Views/themes/vuexy/components/chat_factura.php index cdfa7115..b06bcdaf 100644 --- a/ci4/app/Views/themes/vuexy/components/chat_factura.php +++ b/ci4/app/Views/themes/vuexy/components/chat_factura.php @@ -1,19 +1,20 @@

    -

    -
    +
    -
    +
    -
    - +

    @@ -82,33 +67,32 @@
    -
    - P + P
    Departamento Producción
    - Consulta sobre el presupuesto P001 + Consulta sobre el presupuesto + P001