messages view

This commit is contained in:
amazuecos
2024-11-28 09:14:59 +01:00
parent 3400c5f77c
commit 093aa42e67
23 changed files with 1030 additions and 19 deletions

View File

@ -761,7 +761,22 @@ $routes->group('mensajes', ['namespace' => 'App\Controllers\Mensajeria'], functi
$routes->post('datatable', 'TarifaAcabados::datatable', ['as' => 'tarifaAcabadoDT']);*/
});
});
$routes->group('messages', ['namespace' => 'App\Controllers\Chat'], function ($routes) {
$routes->get('datatable', 'ChatController::datatable_messages', ['as' => 'getDatatableMessages']);
$routes->post('direct', 'ChatController::store_new_direct_message', ['as' => 'storeNewDirectMessage']);
});
$routes->group('chat', ['namespace' => 'App\Controllers\Chat'], function ($routes) {
$routes->get('direct/(:num)', 'ChatController::get_chat_direct_view/$1', ['as' => 'getChatDirectView']);
$routes->get('direct/conversation/(:num)', 'ChatController::get_chat_direct/$1', ['as' => 'getChatDirect']);
$routes->get('direct/users/select/(:num)', 'ChatController::get_chat_direct_select_users/$1', ['as' => 'getChatDirectSelectUsers']);
$routes->get('direct/users/(:num)', 'ChatController::get_chat_direct_users', ['as' => 'getChatDirectUsers']);
$routes->post('direct/users/(:num)', 'ChatController::store_chat_direct_users/$1', ['as' => 'storeChatDirectUsers']);
$routes->get('direct/messages/(:num)', 'ChatController::get_chat_direct_messages/$1', ['as' => 'getChatDirectMessages']);
$routes->post('direct/messages/(:num)', 'ChatController::store_chat_direct_message/$1', ['as' => 'storeChatDirectMessages']);
$routes->get('departments', 'ChatController::get_chat_departments', ['as' => 'getChatDepartments']);
$routes->get('department/presupuesto/(:num)/(:num)', 'ChatController::get_chat_presupuesto/$1/$2', ['as' => 'getChatPresupuesto']);
$routes->get('department/pedido/(:num)/(:num)', 'ChatController::get_chat_pedido/$1/$2', ['as' => 'getChatPedido']);

View File

@ -11,10 +11,13 @@ use App\Models\ChatNotification;
use App\Models\ChatUser;
use App\Models\Clientes\ClienteModel;
use App\Models\Usuarios\UserModel;
use App\Services\MessageService;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\Log\Logger;
use Psr\Log\LoggerInterface;
use Hermawan\DataTables\DataTable;
use CodeIgniter\I18n\Time;
class ChatController extends BaseController
{
@ -27,7 +30,7 @@ class ChatController extends BaseController
protected ChatUser $chatUserModel;
protected ChatNotification $chatNotificationModel;
protected static $viewPath = 'themes/vuexy/form/mensajes/';
public function initController(
@ -108,6 +111,16 @@ class ChatController extends BaseController
$data["chat"] = $chat;
return $this->response->setJSON($data);
}
public function get_chat_direct_view($chat_id){
$chat = $this->chatModel->find($chat_id);
$this->viewData['breadcrumb'] = [
['title' => lang("Chat.chat"), 'route' => route_to("mensajeriaView"), 'active' => false],
['title' => $chat->title, 'route' => 'javascript:void(0);', 'active' => true]
];
$this->viewData["chatId"] = $chat_id;
return view(static::$viewPath . 'messageChat', $this->viewData);
}
public function get_chat(int $chat_id)
{
@ -428,5 +441,75 @@ class ChatController extends BaseController
return $this->response->setJSON($data);
}
public function datatable_messages()
{
$query = $this->chatModel->getQueryDatatable();
$auth_user_id = auth()->user()->id;
return DataTable::of($query)
->edit('created_at',fn($q) => Time::createFromFormat('Y-m-d H:i:s',$q->created_at)->format("d/m/Y H:i"))
->edit('updated_at',fn($q) => Time::createFromFormat('Y-m-d H:i:s',$q->updated_at)->format("d/m/Y H:i"))
->add("creator", fn($q) => $this->chatModel->getChatFirstUser($q->id)->userFullName)
->add("viewed", fn($q) => $this->chatModel->isMessageChatViewed($q->id,$auth_user_id))
->add("action", fn($q) => $q->id)
->toJson(true);
}
public function store_new_direct_message()
{
$bodyData = $this->request->getPost();
$rules = [
"title" => "required|string",
"message" => "required|string",
"users" => "required",
];
if(!$this->validate($rules)){
return $this->response->setStatusCode(400)->setJSON([
'message' => lang('App.global_alert_save_error'),
'status' => 'error',
'errors' => $this->validator->getErrors(),
]);
}
$this->chatModel->createNewDirectChat(...$bodyData);
return $this->response->setJSON(["message" => lang("Chat.new_message_ok"),"status" => true]);
}
public function get_chat_direct($chat_id){
$chatData = $this->chatModel->getChatDirect($chat_id);
return $this->response->setJSON($chatData);
}
public function get_chat_direct_select_users($chat_id){
$chat_users_id = $this->chatUserModel->getChatUserArrayId($chat_id);
$query = $this->userModel->builder()->select(
[
"id",
"CONCAT(first_name,' ',last_name,'(',username,')') as name"
]
)->where("cliente_id", null)
->where("deleted_at", null)
->whereNotIn("id",$chat_users_id);
if ($this->request->getGet("q")) {
$query->groupStart()
->orLike("users.username", $this->request->getGet("q"))
->orLike("CONCAT(first_name,' ',last_name)", $this->request->getGet("q"))
->groupEnd();
}
return $this->response->setJSON($query->get()->getResultObject());
}
public function store_chat_direct_users($chat_id){
$bodyData = $this->request->getPost();
$chat_users = array_map(fn($q) => ["chat_id" => $chat_id,"user_id" => $q],$bodyData["users"]);
$this->chatUserModel->insertBatch($chat_users);
return $this->response->setJSON(["message" => "ok","status" => true]);
}
public function store_chat_direct_message(int $chat_id){
$bodyData = $this->request->getPost();
$bodyData["sender_id"] = auth()->user()->id;
$chat_message_id = $this->chatMessageModel->insert($bodyData);
$message = $this->chatMessageModel->get_chat_message($chat_message_id);
return $this->response->setJSON($message);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Database\Seeds;
use App\Models\Chat\ChatDeparmentModel;
use App\Models\Chat\ChatDeparmentUserModel;
use App\Models\Chat\ChatMessageModel;
use App\Models\Chat\ChatModel;
use App\Models\ChatNotification;
use CodeIgniter\Database\Seeder;
use App\Models\Usuarios\UserModel;
class MessageSeeder extends Seeder
{
public function run()
{
$data = [
"title" => "Message Test",
"messages" => [
[
"sender_id" => 1,
"receiver_id" => 127,
],
[
"sender_id" => 127,
"receiver_id" => 1,
]
]
];
$chatModel = model(ChatModel::class);
$chatMessageModel = model(ChatMessageModel::class);
$chatNotificationModel = model(ChatNotification::class);
$userModel = model(UserModel::class);
foreach (range(1,100) as $key => $value) {
$chat_id = $chatModel->insert(["title" => $data["title"]." ".$value]);
foreach ($data["messages"] as $key => $value) {
$first_name = $userModel->find($value["receiver_id"])?->first_name;
$chat_message_id = $chatMessageModel->insert([
"chat_id" => $chat_id,
"sender_id" => $value["sender_id"],
"receiver_id" => $value["receiver_id"],
"message" => "Hola"." ".$first_name
]);
$chatNotificationModel->insert([
"chat_message_id" => $chat_message_id,
"user_id" => $value["receiver_id"]
]);
}
}
}
}

View File

@ -10,5 +10,17 @@ return [
"new_receivers" => "Nuevos participantes",
"btn_send" => "Enviar",
"btn_send_update" => "Enviar"
]
],
"datatable_messages" => [
"created_at" => "Fecha creación",
"updated_at" => "Fecha actualización",
"title" => "Título",
"creator" => "Creador",
"viewed" => "Leído",
],
"new_message_ok" => "Mensaje enviado correctamente",
"participants" => "Participantes",
"write_message_placeholder" => "Escriba aquí su mensaje..."
];

View File

@ -5,14 +5,15 @@ namespace App\Models\Chat;
use App\Models\ChatNotification;
use App\Models\Usuarios\UserModel;
use CodeIgniter\Model;
use CodeIgniter\Database\BaseBuilder;
class ChatMessageModel extends Model
{
protected $table = 'chat_messages';
protected $primaryKey = 'id';
protected $useAutoIncrement = true;
protected $returnType = 'array';
protected $useSoftDeletes = false;
protected $returnType = 'object';
protected $useSoftDeletes = true;
protected $protectFields = true;
protected $allowedFields = [
"message",
@ -29,7 +30,7 @@ class ChatMessageModel extends Model
protected array $castHandlers = [];
// Dates
protected $useTimestamps = false;
protected $useTimestamps = true;
protected $dateFormat = 'datetime';
protected $createdField = 'created_at';
protected $updatedField = 'updated_at';
@ -72,6 +73,23 @@ class ChatMessageModel extends Model
}
return $messages;
}
public function get_chat_message(int $chat_message_id): object
{
$user = model(UserModel::class);
$auth_user = auth()->user();
$message = $this->find($chat_message_id);
$message->pos = $auth_user->id == $message->sender_id ? "right" : "left";
if ($auth_user->id == $message->sender_id) {
$message->sender_first_name = $auth_user->first_name;
$message->sender_last_name = $auth_user->last_name;
} else {
$sender_user = $user->find($message->sender_id);
$message->sender_first_name = $sender_user->first_name;
$message->sender_last_name = $sender_user->last_name;
}
return $message;
}
public function get_chat_contact_messages(int $receiver_id): array
{
$conversationArray = [];

View File

@ -3,12 +3,13 @@
namespace App\Models\Chat;
use App\Models\ChatNotification;
use App\Models\ChatUser;
use App\Models\Facturas\FacturaModel;
use App\Models\Pedidos\PedidoModel;
use App\Models\Presupuestos\PresupuestoModel;
use App\Models\Usuarios\UserModel;
use CodeIgniter\Model;
use stdClass;
use CodeIgniter\Database\BaseBuilder;
class ChatModel extends Model
{
@ -623,5 +624,126 @@ class ChatModel extends Model
return $q;
}
public function getChatFirstUser(int $chat_id) : object
{
$q = $this->builder()->select(["users.id",
"CONCAT(users.first_name,' ',users.last_name) as userFullName",
])
->join("chat_messages","chat_messages.chat_id = chats.id",'left')
->join("users","users.id = chat_messages.sender_id","left")
->where("chats.id",$chat_id)
->where("chat_messages.deleted_at",null)
->orderBy("chat_messages.created_at",'ASC');
return $q->get()->getFirstRow();
}
/**
* Check if all messages of a chat sended to an user have been viewed.
*
* @param integer $chat_id
* @param integer $user_id
* @return boolean True : All messages readed
*/
public function isMessageChatViewed(int $chat_id, int $user_id) : bool
{
$q = $this->builder()->select(["chat_notifications.id"])
->join("chat_messages","chat_messages.chat_id = chats.id",'left')
->join("chat_notifications","chat_notifications.chat_message_id = chat_messages.id",'left')
->where("chats.id",$chat_id)
->where("chat_notifications.user_id",$user_id)
->where("chat_notifications.viewed",false);
$unread_messages_count = $q->countAllResults();
if($unread_messages_count > 0){
$result = false;
}else{
$result = true;
}
return $result;
}
public function getQueryDatatable(): BaseBuilder
{
$query = $this->builder()
->select([
"chats.id",
"chats.created_at",
"chats.updated_at",
"chats.title",
])
->where("chat_department_id",null)
->where("pedido_id",null)
->where("presupuesto_id",null)
->where("factura_id",null)
->where("title is NOT NULL",NULL,FALSE)
->orderBy("created_at", "DESC");
return $query;
}
public function createNewDirectChat(string $title,string $message,array $users)
{
$chatMessageModel = model(ChatMessageModel::class);
$chatNotificationModel = model(ChatNotification::class);
$chatUserModel = model(ChatUser::class);
$auth_user_id = auth()->user()->id;
$chat_id = $this->insert(["title" => $title]);
$chat_message_id = $chatMessageModel->insert([
"chat_id" => $chat_id,
"sender_id" => $auth_user_id,
"receiver_id" => null,
"message" => $message
]);
$chatUserModel->insert(["chat_id" => $chat_id,"user_id" => $auth_user_id]);
foreach ($users as $key => $user_id) {
$chatUserModel->insert(["chat_id" => $chat_id,"user_id" => $user_id]);
$chatNotificationModel->insert(["chat_message_id" => $chat_message_id,"user_id" => $user_id]);
}
}
/**
* Obtain users and messages from `chat_id`
*
* @param integer $chat_id
* @return array
* [
* "chat" => object,
* "messages" => array ,
* "users" => array
* ]
*/
public function getChatDirect(int $chat_id) : array
{
$auth_user = auth()->user()->id;
$chat = $this->find($chat_id);
$query = $this->builder()->select([
"users.*",
])
->join("chat_users","chat_users.chat_id = chats.id")
->join("users","users.id = chat_users.user_id","left")
->where("chats.id",$chat_id);
$users = $query->get()->getResultObject();
$query = $this->builder()->select([
"chat_messages.*",
"users.first_name as sender_first_name",
"users.last_name as sender_last_name",
])
->join("chat_messages","chat_messages.chat_id = chats.id","left")
->join("users","chat_messages.sender_id = users.id","left")
->where("chats.id",$chat_id);
$messages = $query->get()->getResultObject();
$validatedMessages = [];
foreach ($messages as $key => $message) {
if($auth_user == $message->sender_id){
$message->pos = 'right';
$validatedMessages[] = $message;
}else{
$message->pos = 'left';
$validatedMessages[] = $message;
}
}
$data = [
"chat" => $chat,
"users" => $users,
"messages" => $validatedMessages
];
return $data;
}
}

View File

@ -47,4 +47,5 @@ class ChatNotification extends Model
protected $afterFind = [];
protected $beforeDelete = [];
protected $afterDelete = [];
}

View File

@ -46,4 +46,12 @@ class ChatUser extends Model
protected $afterFind = [];
protected $beforeDelete = [];
protected $afterDelete = [];
public function getChatUserArrayId(int $chat_id) : array
{
$queryResult = $this->builder()
->select(['chat_users.user_id'])
->where("chat_users.chat_id",$chat_id)
->get()->getResultObject();
return array_map(fn($q) => $q->user_id,$queryResult);
}
}

View File

@ -91,5 +91,4 @@ class UserModel extends ShieldUserModel
return $result === null;
}
}

View File

@ -0,0 +1,11 @@
<div class="alert alert-success d-flex align-items-baseline d-none" role="alert" id="<?= $id ?>">
<span class="alert-icon alert-icon-lg text-primary me-2">
<i class="ti ti-sm icon-alert"></i>
</span>
<div class="d-flex flex-column ps-1">
<h5 class="alert-heading mb-2"></h5>
<div class="alert-body">
</div>
</div>
</div>

View File

@ -0,0 +1,154 @@
<div class="container-xxl flex-grow-1 container-p-y" id="chat-direct-message" data-id="<?= $modelId ?>">
<div class="app-chat card overflow-hidden">
<div class="row g-0">
<!-- Chat & Contacts -->
<div class="col app-chat-contacts app-sidebar flex-grow-0 overflow-hidden border-end"
id="app-chat-contacts">
<div class="sidebar-header">
<div class="d-flex align-items-center me-3 me-lg-0">
<div class="flex-shrink-0 avatar me-3">
<button class="btn btn-primary btn-icon me-2" id="btn-chat-add-participant"><span class="ti ti-md ti-plus"></span></button>
</div>
<div class="flex-grow-1 input-group input-group-merge rounded-pill">
<span class="input-group-text" id="basic-addon-search31">
<i class="ti ti-search"></i>
</span>
<input type="text" class="form-control chat-search-input"
placeholder="Buscar..." aria-label="Buscar..."
aria-describedby="basic-addon-search31" />
</div>
</div>
<!-- <i class="ti ti-x cursor-pointer d-lg-none d-block position-absolute mt-2 me-1 top-0 end-0"
data-overlay data-bs-toggle="sidebar" data-target="#app-chat-contacts"></i> -->
</div>
<div class="sidebar-body">
<!-- CLIENTES -->
<ul class="list-unstyled chat-contact-list mb-0">
<li class="chat-contact-list-item chat-contact-list-item-title">
<h5 class="text-primary mb-0"><?= lang("Chat.participants") ?></h5>
</li>
</ul>
<ul class="list-unstyled chat-contact-list mb-0" id="contact-list">
<div class="d-flex justify-content-center chat-loader">
<div class="sk-wave sk-primary ">
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
</div>
</div>
<!-- <li class="chat-contact-list-item">
<a class="d-flex align-items-center">
<div class="avatar d-block flex-shrink-0">
<span class="avatar-initial rounded-circle bg-label-primary">JJ</span>
</div>
<div class="chat-contact-info flex-grow-1 ms-2">
<h6 class="chat-contact-name text-truncate m-0">Jaime Jimenez</h6>
</div>
</a>
</li> -->
</ul>
</div>
</div>
<!-- /Chat contacts -->
<!-- Chat History -->
<div class="col app-chat-history bg-body">
<div class="chat-history-wrapper ">
<div class="chat-history-header border-bottom">
<div class="d-flex justify-content-between align-items-center">
<div class="d-flex overflow-hidden align-items-center">
<i class="ti ti-menu-2 ti-sm cursor-pointer d-lg-none d-block me-2"
data-bs-toggle="sidebar" data-overlay
data-target="#app-chat-contacts"></i>
<h5 class="text-primary mb-0" id="chat-direct-title"><br></h5>
<div class="chat-contact-info flex-grow-1 ms-2">
<h6 class="m-0"></h6>
<small class="user-status text-muted"></small>
</div>
</div>
<!-- <div class="d-flex align-items-center">
<div class="dropdown d-flex align-self-center">
<button class="btn p-0" type="button" id="chat-header-actions"
data-bs-toggle="dropdown" aria-haspopup="true"
aria-expanded="false">
<i class="ti ti-dots-vertical"></i>
</button>
<div class="dropdown-menu dropdown-menu-end"
aria-labelledby="chat-header-actions">
<a class="dropdown-item" href="javascript:void(0);">Silenciar
Conversacion</a>
<a class="dropdown-item" href="javascript:void(0);">Limpiar
Conversacion</a>
</div>
</div>
</div> -->
</div>
</div>
<div class="chat-history-body bg-body ">
<div class="d-flex justify-content-center chat-loader">
<div class="sk-wave sk-primary ">
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
<div class="sk-wave-rect"></div>
</div>
</div>
<ul class="list-unstyled chat-history" id="chat-conversation">
</ul>
</div>
<!-- Chat message form -->
<div class="chat-history-footer shadow-sm">
<div
class="form-send-message d-flex justify-content-between align-items-center">
<input class="form-control message-input border-0 me-3 shadow-none"
placeholder="<?= lang("Chat.write_message_placeholder") ?>" />
<div class="message-actions d-flex align-items-center">
<div class="message-actions d-flex align-items-center">
<a class="btn btn-primary d-flex send-msg-btn" style="color:white"
id="send-msg-btn-direct">
<i class="ti ti-send me-md-1 me-0"></i>
<span class="align-middle d-md-inline-block"><?= lang("Chat.modal.btn_send") ?></span>
</a>
</div>
</div>
</div>
</div>
</div>
<!-- /Chat History -->
<div class="app-overlay"></div>
</div>
</div>
</div>
<?= view("themes/vuexy/components/modals/modalAddNewChatParticipant") ?>
</div>
<?= $this->section('css') ?>
<link rel="stylesheet" href="<?= site_url('themes/vuexy/vendor/libs/spinkit/spinkit.css') ?>" />
<link rel="stylesheet" href="<?= site_url('themes/vuexy/vendor/css/pages/app-chat.css') ?>">
<?= $this->endSection() ?>
<!------------------------------------------------------->
<!-- Código JS logica -->
<!------------------------------------------------------->
<?= $this->section("additionalExternalJs") ?>
<script type="module" src="<?= site_url('assets/js/safekat/pages/messages/directMessagePage.js') ?>"></script>
<script src="<?= site_url('themes/vuexy/vendor/libs/perfect-scrollbar/perfect-scrollbar.js') ?>"></script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,31 @@
<!-- Modal -->
<div class="modal fade" id="modalAddNewChatParticipant" tabindex="-1" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel1"><?= lang('Chat.modal.new_receivers') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="col-12 mb-0">
<?= view("themes/vuexy/components/alerts/alert", ["id" => "alertNewParticipant"]) ?>
</div>
<form id="formNewParticipant">
<div class="form-group">
<div class="row">
<div class="col-12 mb-0">
<label for="select-users" class="required form-label"><?= lang('Chat.modal.new_receivers') ?></label>
<select id="select-users" name="users" class="select2 form-control" multiple required>
</select>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal"><?= lang('App.global_come_back') ?></button>
<button type="button" id="btn-chat-add-participant-submit" class="btn btn-primary d-none"><?= lang('App.global_save') ?></button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,41 @@
<!-- Modal -->
<div class="modal fade" id="modalNewDirectMessage" tabindex="-1" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel1"><?= lang('Chat.modal.new_message') ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="col-12 mb-0">
<?= view("themes/vuexy/components/alerts/alert", ["id" => "alertDirectMessage"]) ?>
</div>
<form id="formNewDirectMessage">
<div class="form-group">
<div class="row">
<div class="col-12 mb-0">
<label for="new-direct-message-title" class="form-label"><?= lang('Chat.modal.title') ?></label>
<input type="input" rows="4" cols="10" id="new-direct-message-title" name="title" placeholder="Escriba un título" name="title" class="form-control" required />
</div>
<div class="col-12 mb-0">
<label for="description" class="required form-label"><?= lang('Chat.modal.new_message') ?></label>
<textarea type="input" rows="4" cols="10" id="new-direct-message-text" name="message" placeholder="Escribe el mensaje ..." name="message" class="form-control" required></textarea>
</div>
<div class="col-12 mb-0">
<label for="select-users" class="required form-label"><?= lang('Chat.modal.new_receivers') ?></label>
<select id="select-users" name="users" class="form-control" multiple required>
<option value="0"></option>
</select>
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-label-secondary" data-bs-dismiss="modal"><?= lang('App.global_come_back') ?></button>
<button type="button" id="submit-new-direct-message" class="btn btn-primary"><?= lang('Chat.modal.btn_send') ?></button>
</div>
</div>
</div>
</div>

View File

@ -1,10 +1,68 @@
<?= $this->include("themes/_commonPartialsBs/select2bs5") ?>
<?= $this->include("themes/_commonPartialsBs/datatables") ?>
<?= $this->include('themes/_commonPartialsBs/select2bs5') ?>
<?= $this->include('themes/_commonPartialsBs/datatables') ?>
<?= $this->include('themes/_commonPartialsBs/_confirm2delete') ?>
<?= $this->extend('themes/vuexy/main/defaultlayout') ?>
<?= $this->section('content'); ?>
<!--Content Body-->
<div class="container-xxl flex-grow-1 container-p-y">
<?= view("themes/vuexy/components/chat_general", data: ["modelId" => null]) ?>
<div class="row">
<div class="col-md-12">
<div class="card card-info">
<div class="card-header">
<h3 class="card-title"><?= lang('Chat.messages') ?></h3>
</div>
<!--//.card-header -->
<div class="card-body" id="messagesViewCard">
<?= view('themes/_commonPartialsBs/_alertBoxes'); ?>
<div class="row">
<div class="col-md-12 d-flex justify-content-start">
<button class="btn btn-primary" id="btn-new-message"><span class="ti ti-md ti-plus"></span>Nuevo mensaje</button>
</div>
</div>
<table id="tableMessages" class="table table-striped table-hover" style="width: 100%;">
<thead>
<tr>
<th><?= lang('Chat.datatable_messages.created_at') ?></th>
<th><?= lang('Chat.datatable_messages.updated_at') ?></th>
<th><?= lang('Chat.datatable_messages.title') ?></th>
<th><?= lang('Chat.datatable_messages.creator') ?></th>
<th><?= lang('Chat.datatable_messages.viewed') ?></th>
<th class="text-nowrap"><?= lang('Basic.global.Action') ?></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<!--//.card-body -->
<div class="card-footer">
</div>
<!--//.card-footer -->
</div>
<!--//.card -->
</div>
<!--//.col -->
</div>
<?= view("themes/vuexy/components/modals/modalNewDirectMessage") ?>
<?= $this->endSection() ?>
<?= $this->section('css') ?>
<link rel="stylesheet" href="<?= site_url('themes/vuexy/vendor/libs/formvalidation/dist/css/formValidation.min.css') ?>" />
<link rel="stylesheet" href="<?= site_url('themes/vuexy/vendor/css/pages/app-chat.css') ?>">
<?= $this->endSection() ?>
<?= $this->section("additionalExternalJs") ?>
<script src="<?= site_url("themes/vuexy/vendor/libs/formvalidation/dist/js/FormValidation.js") ?>"></script>
<script src="<?= site_url("themes/vuexy/vendor/libs/formvalidation/dist/js/plugins/Bootstrap5.min.js") ?>"></script>
<script src="<?= site_url("themes/vuexy/vendor/libs/formvalidation/dist/js/plugins/AutoFocus.min.js") ?>"></script>
<script type="module" src="<?= site_url('assets/js/safekat/pages/configuracion/messages/index.js') ?>">
</script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,27 @@
<?= $this->include('themes/_commonPartialsBs/select2bs5') ?>
<?= $this->include('themes/_commonPartialsBs/datatables') ?>
<?= $this->include('themes/_commonPartialsBs/_confirm2delete') ?>
<?= $this->extend('themes/vuexy/main/defaultlayout') ?>
<?= $this->section('content'); ?>
<!--Content Body-->
<div class="row">
<?= view("themes/vuexy/components/chat_direct",["modelId" => $chatId]) ?>
</div>
<?= $this->endSection() ?>
<?= $this->section('css') ?>
<link rel="stylesheet" href="<?= site_url('themes/vuexy/vendor/libs/formvalidation/dist/css/formValidation.min.css') ?>" />
<link rel="stylesheet" href="<?= site_url('themes/vuexy/vendor/css/pages/app-chat.css') ?>">
<?= $this->endSection() ?>
<?= $this->section("additionalExternalJs") ?>
<script src="<?= site_url("themes/vuexy/vendor/libs/formvalidation/dist/js/FormValidation.js") ?>"></script>
<script src="<?= site_url("themes/vuexy/vendor/libs/formvalidation/dist/js/plugins/Bootstrap5.min.js") ?>"></script>
<script src="<?= site_url("themes/vuexy/vendor/libs/formvalidation/dist/js/plugins/AutoFocus.min.js") ?>"></script>
</script>
<?= $this->endSection() ?>

View File

@ -0,0 +1,53 @@
class Alert {
constructor(domItem) {
this.item = domItem
this.headingTitle = this.item.find(".alert-heading")
this.body = this.item.find(".alert-body")
this.icon = this.item.find(".icon-alert")
this.iconSuccess = "ti-circle-check"
this.iconError = "ti-exclamation-mark"
}
setIcon(iconClass){
this.icon.removeClass(this.iconSuccess)
this.icon.removeClass(this.iconError)
this.icon.addClass(iconClass)
}
setAsError() {
this.item.removeClass("alert-success")
this.item.addClass("alert-danger")
this.setIcon(this.iconError)
}
setAsSuccess() {
this.item.removeClass("alert-danger")
this.item.addClass("alert-success")
this.setIcon(this.iconSuccess)
}
setAsWarning() {
this.item.removeClass("alert-success")
this.item.addClass("alert-warning")
}
setAsInfo() {
this.item.removeClass("alert-*")
this.item.addClass("alert-info")
}
show() {
this.item.removeClass("d-none")
}
hide() {
this.item.addClass("d-none")
}
setHeadingTitle(title) {
this.headingTitle.text(title)
}
setContent(content) {
this.body.append(content)
}
setErrors() { }
}
export default Alert;

View File

@ -1,4 +1,6 @@
import Ajax from '../components/ajax.js'
import Modal from './modal.js'
import ClassSelect from './select2.js'
@ -66,12 +68,38 @@ class Chat {
});
}
}
initDirectMessage() {
this.chatType = "direct"
this.modalNewParticipant = new Modal(this.domItem.find("#modalAddNewChatParticipant"))
this.selectPlaceholder = {
id: '0',
text: "Seleccione un usuario"
}
this.btnDirectMessageSubmit = this.domItem.find("#send-msg-btn-direct")
this.btnDirectMessageSubmit.on("click", this._handleStoreChatDirectMessage.bind(this))
this.selectParticipants = new ClassSelect(this.modalNewParticipant.item.find("#select-users"), `/chat/direct/users/select/${this.modelId}`, this.selectPlaceholder, true)
this.btnAddParticipant = this.domItem.find("#btn-chat-add-participant")
this.btnAddParticipantSubmit = this.domItem.find("#btn-chat-add-participant-submit")
this.btnAddParticipantSubmit.on("click", this._handleStoreChatDirectUsers.bind(this))
this.btnAddParticipant.on("click", () => {
this.selectParticipants.init()
this.modalNewParticipant.toggle()
})
this.selectParticipants.item.on("change", () => {
console.log(this.selectParticipants.getVal())
if (this.selectParticipants.getVal().length > 0) {
this.btnAddParticipantSubmit.removeClass("d-none")
} else {
this.btnAddParticipantSubmit.addClass("d-none")
}
})
this._handleGetChatDirect()
}
initGeneral() {
this.chatType = "general"
this._handleGetChatList()
this.sendBtnMessageDepartment.on("click", this._sendMessage.bind(this))
}
initPresupuesto() {
this.chatType = "presupuesto"
@ -236,8 +264,7 @@ class Chat {
);
ajax.get();
}
_getChatDepartmentMessageCount()
{
_getChatDepartmentMessageCount() {
let ajax = new Ajax(
`/chat/department/count/${this.chatType}/${this.chatDeparmentId}/${this.modelId}`,
null,
@ -249,10 +276,10 @@ class Chat {
);
ajax.get();
}
_getChatDepartmentMessageCountSuccess(data){
_getChatDepartmentMessageCountSuccess(data) {
this.domItem.find(`chat_${data.name}`).find(".messages-unread-contact").text(data.count)
}
_getChatDepartmentMessageCountError(){}
_getChatDepartmentMessageCountError() { }
_getChatMessage() {
let ajax = new Ajax(
@ -293,7 +320,7 @@ class Chat {
<i class="ti me-1"></i>
<small>${chatMessage?.user?.first_name + " " + chatMessage?.user?.last_name}</small>
</div>
<i class="ti ti-checks ti-xs me-1 text-success"></i>
<i class="ti ${chatMessage?.viewed ? "ti-checks" : "ti-check"} ti-xs me-1 text-success"></i>
<small>${chatMessage.created_at}</small>
</div>
@ -309,6 +336,37 @@ class Chat {
this.chatHistory.append(chatItem)
return chatItem
}
_addChatDirectMessages(chatMessage) {
console.log(chatMessage)
let chatItem = `
<li class="chat-message chat-message-${chatMessage.pos}">
<div class="d-flex overflow-hidden">
<div class="chat-message-wrapper flex-grow-1">
<div class="chat-message-text">
<p class="mb-0">${chatMessage?.message}</p>
</div>
<div class="text-end text-muted mt-1">
<div class="text-start text-muted mt-1">
<i class="ti me-1"></i>
<small>${chatMessage?.sender_first_name + " " + chatMessage?.sender_last_name}</small>
</div>
<i class="ti ${chatMessage?.viewed == "1" ? "ti-checks" : "ti-check"} ti-xs me-1 text-success"></i>
<small>${chatMessage.created_at}</small>
</div>
</div>
<div class="user-avatar flex-shrink-0 ms-3">
<div class="avatar avatar-sm">
<span class="avatar-initial rounded-circle bg-label-primary">${chatMessage?.sender_first_name.charAt(0) + chatMessage?.sender_last_name.charAt(0)}</span>
</div>
</div>
</div>
</li>
`
this.chatHistory.append(chatItem)
return chatItem
}
_sendMessagePressKey(e) {
if (e.which == 13) {
e.preventDefault();
@ -500,6 +558,104 @@ class Chat {
this.sideBar.find("#contact-list").append(contactItem)
}
}
_addParticipantToList(contact) {
let contactItem =
`
<li class="chat-contact-list-item">
<a class="d-flex align-items-center contact-chat" data-id="${contact.id}"
id="chat-contact-list-item-${contact.id}">
<div class="avatar d-block flex-shrink-0">
<span class="avatar-initial rounded-circle bg-label-primary">
${contact?.first_name?.charAt(0) ?? "?"
+ contact?.last_name?.charAt(0) ?? "?"}</span>
</div>
<div class="chat-contact-info flex-grow-1 ms-2">
<h6 class="chat-contact-name text-truncate m-0">${contact?.first_name ?? "" + " " +
contact?.last_name ?? ""}</h6>
<p class="chat-contact-status text-muted text-truncate mb-0">
${contact?.cliente_id ? "[CLIENTE]" : ""}${contact.username}
</p>
</div>
</a>
</li>
`
if (contact.first_name || contact.last_name) {
this.sideBar.find("#contact-list").append(contactItem)
}
}
_handleGetChatDirect() {
const ajax = new Ajax(
`/chat/${this.chatType}/conversation/${this.modelId}`,
null,
null,
this._handleGetChatDirectSuccess.bind(this),
this._handleGetChatDirectError.bind(this)
)
ajax.get()
}
_handleGetChatDirectSuccess(response) {
const { chat, users, messages } = response
if (users.length > 0) {
users.map(c => this._addContactToList(c))
}
if (messages.length > 0) {
messages.map(m => this._addChatDirectMessages(m))
}
this.domItem.find("#chat-direct-title").text(chat.title)
this.domItem.find(".chat-loader").addClass("d-none")
}
_handleGetChatDirectError(error) { }
_handleStoreChatDirectUsers() {
const data = { "users": this.selectParticipants.getVal() }
this.domItem.find(".chat-loader").removeClass("d-none")
const ajax = new Ajax(
`/chat/${this.chatType}/users/${this.modelId}`,
data,
null,
this._handleStoreChatDirectUsersSuccess.bind(this),
this._handleStoreChatDirectUsersError.bind(this)
)
ajax.post()
}
_handleStoreChatDirectUsersSuccess(response) {
this.domItem.find(".chat-loader").removeClass("d-none")
this.chatHistory.empty()
this.sideBar.find("#contact-list").empty()
this.modalNewParticipant.toggle()
this._handleGetChatDirect()
this.selectParticipants.reset()
}
_handleStoreChatDirectUsersError() {
this.domItem.find(".chat-loader").removeClass("d-none")
}
_handleStoreChatDirectMessage() {
const data = { "message": this.messageInput.val(), "chat_id": this.modelId }
if (data.message) {
const ajax = new Ajax(
`/chat/${this.chatType}/messages/${this.modelId}`,
data,
null,
this._handleStoreChatDirectMessageSuccess.bind(this),
this._handleStoreChatDirectMessageError.bind(this)
)
ajax.post()
}
}
_handleStoreChatDirectMessageSuccess(response) {
let message = response
this._addChatDirectMessages(message)
this.messageInput.val("")
}
_handleStoreChatDirectMessageError(error) { }

View File

@ -0,0 +1,52 @@
import Ajax from "../ajax.js";
class MessagesDatatable {
constructor(domItem) {
this.item = domItem
this.datatableItem = this.item.find("#tableMessages")
this.datatableColumns = [
{ data: 'created_at', searchable: true, sortable: true },
{ data: 'updated_at', searchable: true, sortable: true },
{ data: 'title', searchable: true, sortable: true },
{ data: 'creator', searchable: false, sortable: false },
{ data: 'viewed', searchable: false, sortable: false ,
render : (d,t) => {
const iconClass = d == true ? "ti ti-sm ti-check" : "ti ti-sm ti-x"
return `<span class="${iconClass}"</span>`
}
},
{
data: 'action', sortable: false, searchable: false,
render: (d, t) => {
return `
<div class="btn-group btn-group-sm">
<a href="/chat/direct/${d}" data-id="${d}" class="message-edit"><i class="ti ti-eye ti-sm mx-2"></i></a>
</div>
`
}
}
]
}
init() {
this.datatable = this.datatableItem.DataTable({
processing: true,
layout: {
topStart: 'pageLength',
topEnd: 'search',
bottomStart: 'info',
bottomEnd: 'paging'
},
serverSide: true,
pageLength: 10,
language: {
url: "/themes/vuexy/vendor/libs/datatables-sk/plugins/i18n/es-ES.json"
},
columns: this.datatableColumns,
ajax: '/messages/datatable'
});
}
}
export default MessagesDatatable;

View File

@ -13,6 +13,7 @@ let ClassSelect = function (domItem, url, placeholder, allowClear = false, param
placeholder: placeholder,
allowClear: allowClear,
dropdownParent: domItem.parent(),
language: "es",
ajax: {
url: () => {
return this.url;
@ -47,7 +48,7 @@ let ClassSelect = function (domItem, url, placeholder, allowClear = false, param
this.init = function () {
if (this.item.length) {
this.item = this.item.select2(this.config);
$.fn.modal.Constructor.prototype.enforceFocus = function () {};
// $.fn.modal.Constructor.prototype.enforceFocus = function () {};
}
};
this.setOption = function (id, nombre) {

View File

@ -0,0 +1,5 @@
import MessagePage from "./messagePage.js";
const messagePage = new MessagePage()
messagePage.init()

View File

@ -0,0 +1,82 @@
import MessagesDatatable from "../../../components/datatables/MessagesDatatable.js";
import Modal from "../../../components/modal.js";
import Ajax from "../../../components/ajax.js";
import ClassSelect from "../../../components/select2.js";
import Alert from "../../../components/alerts/alert.js";
class MessagePage {
constructor() {
this.messageDatatable = new MessagesDatatable($("#messagesViewCard")) // mensajesView.php
this.modalNewMessage = new Modal($("#modalNewDirectMessage"))
this.btnNewMessage = $("#btn-new-message")
this.btnSubmitNewDirectMessage = this.modalNewMessage.item.find("#submit-new-direct-message")
this.formNewDirectMessage = this.modalNewMessage.item.find("#formNewDirectMessage")
this.alert = new Alert($("#alertDirectMessage"))
this.selectUsers = $("#select-users")
this.selectPlaceholder = {
id: '0',
text: "Seleccione un usuario"
}
this.selectMessageUsers = new ClassSelect(this.selectUsers, '/chat/users/internal', this.selectPlaceholder, true)
}
init() {
this.events()
this.messageDatatable.init()
}
events() {
// Open new message when click in btn-new-message
this.btnNewMessage.on("click", this.openNewMessageModal.bind(this))
this.btnSubmitNewDirectMessage.on("click", this.handleSubmitNewMessage.bind(this))
}
openNewMessageModal() {
this.formNewDirectMessage.trigger("reset")
this.selectMessageUsers.init()
this.alert.hide()
this.showForm()
this.modalNewMessage.toggle()
}
handleSubmitNewMessage() {
this.btnNewMessage.addClass("loading")
const ajax = new Ajax("/messages/direct",
this.getNewMessageDataForm(),
null,
this.handleSubmitNewMessageSuccess.bind(this),
this.handleSubmitNewMessageError.bind(this))
ajax.post()
}
handleSubmitNewMessageSuccess(response) {
this.btnNewMessage.removeClass("loading")
this.alert.setAsSuccess()
this.alert.setHeadingTitle(response.message)
this.alert.show()
this.hideForm()
}
handleSubmitNewMessageError(response) {
const error = response.responseJSON
this.btnNewMessage.removeClass("loading")
this.alert.show()
this.alert.setAsError()
this.alert.setHeadingTitle(error.message)
}
getNewMessageDataForm() {
return {
"title": this.formNewDirectMessage.find("#new-direct-message-title").val(),
"message": this.formNewDirectMessage.find("#new-direct-message-text").val(),
"users": this.selectMessageUsers.getVal()
}
}
hideForm() {
this.formNewDirectMessage.addClass("d-none")
this.btnSubmitNewDirectMessage.addClass("d-none")
}
showForm() {
this.formNewDirectMessage.removeClass("d-none")
this.btnSubmitNewDirectMessage.removeClass("d-none")
}
}
export default MessagePage;

View File

@ -0,0 +1,9 @@
import Chat from "../../components/chat.js";
$(function(){
const chatDirectMessage = new Chat($("#chat-direct-message"))
chatDirectMessage.init()
chatDirectMessage.initDirectMessage()
}
)

View File

@ -0,0 +1,21 @@
export const newDirectMessageValidation =
{
fields:{
title:{
notEmpty:{
message : "Título no puede estar vacío"
},
},
message:{
notEmpty:{
message : "Mensaje no puede estar vacío"
},
},
users:{
notEmpty:{
message : "Debe seleccionar al menos a un participante"
},
}
},
}