diff --git a/.gitignore b/.gitignore index d0eb167..e427ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,3 @@ node_modules/ */node_modules/ */build/ - -**/node_modules/ -**/build/ -.DS_Store -.env \ No newline at end of file diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json index 797c1b2..35e2991 100644 --- a/app-shell/src/_schema.json +++ b/app-shell/src/_schema.json @@ -1,6 +1,5 @@ + + { - "Initial version": "{\"iv\":\"Dn/UUznLNSj+kA/t\",\"encryptedData\":\"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\"}", - "v1.1": "{\"iv\":\"z9N5LBZFeA+wx7x6\",\"encryptedData\":\"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\"}", - "1.2": "{\"iv\":\"tmNadHeR+va8R0Ds\",\"encryptedData\":\"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\"}", - "": "{\"iv\":\"8zzrCByO/qjYOnhE\",\"encryptedData\":\"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\"}" -} \ No newline at end of file + "Initial version": "{\"iv\":\"Dn/UUznLNSj+kA/t\",\"encryptedData\":\"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\"}" +} diff --git a/backend/src/db/api/conversations.js b/backend/src/db/api/conversations.js deleted file mode 100644 index 394297f..0000000 --- a/backend/src/db/api/conversations.js +++ /dev/null @@ -1,355 +0,0 @@ -const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); -const Utils = require('../utils'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class ConversationsDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversations = await db.conversations.create( - { - id: data.id || undefined, - - title: data.title || null, - createdat: data.createdat || null, - updatedat: data.updatedat || null, - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - await conversations.setUser(data.user || null, { - transaction, - }); - - return conversations; - } - - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - // Prepare data - wrapping individual data transformations in a map() method - const conversationsData = data.map((item, index) => ({ - id: item.id || undefined, - - title: item.title || null, - createdat: item.createdat || null, - updatedat: item.updatedat || null, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const conversations = await db.conversations.bulkCreate(conversationsData, { - transaction, - }); - - // For each item created, replace relation files - - return conversations; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversations = await db.conversations.findByPk( - id, - {}, - { transaction }, - ); - - const updatePayload = {}; - - if (data.title !== undefined) updatePayload.title = data.title; - - if (data.createdat !== undefined) updatePayload.createdat = data.createdat; - - if (data.updatedat !== undefined) updatePayload.updatedat = data.updatedat; - - updatePayload.updatedById = currentUser.id; - - await conversations.update(updatePayload, { transaction }); - - if (data.user !== undefined) { - await conversations.setUser( - data.user, - - { transaction }, - ); - } - - return conversations; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversations = await db.conversations.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of conversations) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of conversations) { - await record.destroy({ transaction }); - } - }); - - return conversations; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversations = await db.conversations.findByPk(id, options); - - await conversations.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await conversations.destroy({ - transaction, - }); - - return conversations; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const conversations = await db.conversations.findOne( - { where }, - { transaction }, - ); - - if (!conversations) { - return conversations; - } - - const output = conversations.get({ plain: true }); - - output.user = await conversations.getUser({ - transaction, - }); - - return output; - } - - static async findAll(filter, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - offset = currentPage * limit; - - const orderBy = null; - - const transaction = (options && options.transaction) || undefined; - - let include = [ - { - model: db.users, - as: 'user', - - where: filter.user - ? { - [Op.or]: [ - { - id: { - [Op.in]: filter.user - .split('|') - .map((term) => Utils.uuid(term)), - }, - }, - { - firstName: { - [Op.or]: filter.user - .split('|') - .map((term) => ({ [Op.iLike]: `%${term}%` })), - }, - }, - ], - } - : {}, - }, - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.title) { - where = { - ...where, - [Op.and]: Utils.ilike('conversations', 'title', filter.title), - }; - } - - if (filter.createdatRange) { - const [start, end] = filter.createdatRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - createdat: { - ...where.createdat, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - createdat: { - ...where.createdat, - [Op.lte]: end, - }, - }; - } - } - - if (filter.updatedatRange) { - const [start, end] = filter.updatedatRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - updatedat: { - ...where.updatedat, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - updatedat: { - ...where.updatedat, - [Op.lte]: end, - }, - }; - } - } - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true', - }; - } - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - const queryOptions = { - where, - include, - distinct: true, - order: - filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log, - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.conversations.findAndCountAll( - queryOptions, - ); - - return { - rows: options?.countOnly ? [] : rows, - count: count, - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } - } - - static async findAllAutocomplete(query, limit, offset) { - let where = {}; - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike('conversations', 'title', query), - ], - }; - } - - const records = await db.conversations.findAll({ - attributes: ['id', 'title'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['title', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.title, - })); - } -}; diff --git a/backend/src/db/api/messages.js b/backend/src/db/api/messages.js deleted file mode 100644 index a660ad0..0000000 --- a/backend/src/db/api/messages.js +++ /dev/null @@ -1,340 +0,0 @@ -const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); -const Utils = require('../utils'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class MessagesDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const messages = await db.messages.create( - { - id: data.id || undefined, - - content: data.content || null, - sender: data.sender || null, - agentname: data.agentname || null, - createdat: data.createdat || null, - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - await messages.setConversation(data.conversation || null, { - transaction, - }); - - return messages; - } - - static async bulkImport(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - // Prepare data - wrapping individual data transformations in a map() method - const messagesData = data.map((item, index) => ({ - id: item.id || undefined, - - content: item.content || null, - sender: item.sender || null, - agentname: item.agentname || null, - createdat: item.createdat || null, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const messages = await db.messages.bulkCreate(messagesData, { - transaction, - }); - - // For each item created, replace relation files - - return messages; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const messages = await db.messages.findByPk(id, {}, { transaction }); - - const updatePayload = {}; - - if (data.content !== undefined) updatePayload.content = data.content; - - if (data.sender !== undefined) updatePayload.sender = data.sender; - - if (data.agentname !== undefined) updatePayload.agentname = data.agentname; - - if (data.createdat !== undefined) updatePayload.createdat = data.createdat; - - updatePayload.updatedById = currentUser.id; - - await messages.update(updatePayload, { transaction }); - - if (data.conversation !== undefined) { - await messages.setConversation( - data.conversation, - - { transaction }, - ); - } - - return messages; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const messages = await db.messages.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of messages) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of messages) { - await record.destroy({ transaction }); - } - }); - - return messages; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const messages = await db.messages.findByPk(id, options); - - await messages.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await messages.destroy({ - transaction, - }); - - return messages; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const messages = await db.messages.findOne({ where }, { transaction }); - - if (!messages) { - return messages; - } - - const output = messages.get({ plain: true }); - - output.conversation = await messages.getConversation({ - transaction, - }); - - return output; - } - - static async findAll(filter, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - offset = currentPage * limit; - - const orderBy = null; - - const transaction = (options && options.transaction) || undefined; - - let include = [ - { - model: db.conversations, - as: 'conversation', - - where: filter.conversation - ? { - [Op.or]: [ - { - id: { - [Op.in]: filter.conversation - .split('|') - .map((term) => Utils.uuid(term)), - }, - }, - { - title: { - [Op.or]: filter.conversation - .split('|') - .map((term) => ({ [Op.iLike]: `%${term}%` })), - }, - }, - ], - } - : {}, - }, - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.content) { - where = { - ...where, - [Op.and]: Utils.ilike('messages', 'content', filter.content), - }; - } - - if (filter.sender) { - where = { - ...where, - [Op.and]: Utils.ilike('messages', 'sender', filter.sender), - }; - } - - if (filter.agentname) { - where = { - ...where, - [Op.and]: Utils.ilike('messages', 'agentname', filter.agentname), - }; - } - - if (filter.createdatRange) { - const [start, end] = filter.createdatRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - createdat: { - ...where.createdat, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - createdat: { - ...where.createdat, - [Op.lte]: end, - }, - }; - } - } - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true', - }; - } - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - const queryOptions = { - where, - include, - distinct: true, - order: - filter.field && filter.sort - ? [[filter.field, filter.sort]] - : [['createdAt', 'desc']], - transaction: options?.transaction, - logging: console.log, - }; - - if (!options?.countOnly) { - queryOptions.limit = limit ? Number(limit) : undefined; - queryOptions.offset = offset ? Number(offset) : undefined; - } - - try { - const { rows, count } = await db.messages.findAndCountAll(queryOptions); - - return { - rows: options?.countOnly ? [] : rows, - count: count, - }; - } catch (error) { - console.error('Error executing query:', error); - throw error; - } - } - - static async findAllAutocomplete(query, limit, offset) { - let where = {}; - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike('messages', 'sender', query), - ], - }; - } - - const records = await db.messages.findAll({ - attributes: ['id', 'sender'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['sender', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.sender, - })); - } -}; diff --git a/backend/src/db/migrations/1746173616791.js b/backend/src/db/migrations/1746173616791.js deleted file mode 100644 index 5c4ab61..0000000 --- a/backend/src/db/migrations/1746173616791.js +++ /dev/null @@ -1,72 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.createTable( - 'conversations', - { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.dropTable('conversations', { transaction }); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/1746173773482.js b/backend/src/db/migrations/1746173773482.js deleted file mode 100644 index 40e20c6..0000000 --- a/backend/src/db/migrations/1746173773482.js +++ /dev/null @@ -1,49 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.addColumn( - 'conversations', - 'updatedat', - { - type: Sequelize.DataTypes.DATE, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.removeColumn('conversations', 'updatedat', { - transaction, - }); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/1746173790909.js b/backend/src/db/migrations/1746173790909.js deleted file mode 100644 index 85fbc72..0000000 --- a/backend/src/db/migrations/1746173790909.js +++ /dev/null @@ -1,72 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.createTable( - 'messages', - { - id: { - type: Sequelize.DataTypes.UUID, - defaultValue: Sequelize.DataTypes.UUIDV4, - primaryKey: true, - }, - createdById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - updatedById: { - type: Sequelize.DataTypes.UUID, - references: { - key: 'id', - model: 'users', - }, - }, - createdAt: { type: Sequelize.DataTypes.DATE }, - updatedAt: { type: Sequelize.DataTypes.DATE }, - deletedAt: { type: Sequelize.DataTypes.DATE }, - importHash: { - type: Sequelize.DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.dropTable('messages', { transaction }); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/1746173858091.js b/backend/src/db/migrations/1746173858091.js deleted file mode 100644 index a323707..0000000 --- a/backend/src/db/migrations/1746173858091.js +++ /dev/null @@ -1,47 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.addColumn( - 'messages', - 'sender', - { - type: Sequelize.DataTypes.TEXT, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.removeColumn('messages', 'sender', { transaction }); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/1746173878197.js b/backend/src/db/migrations/1746173878197.js deleted file mode 100644 index fef8002..0000000 --- a/backend/src/db/migrations/1746173878197.js +++ /dev/null @@ -1,49 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.addColumn( - 'messages', - 'agentname', - { - type: Sequelize.DataTypes.TEXT, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.removeColumn('messages', 'agentname', { - transaction, - }); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/migrations/1746173895643.js b/backend/src/db/migrations/1746173895643.js deleted file mode 100644 index ac84eff..0000000 --- a/backend/src/db/migrations/1746173895643.js +++ /dev/null @@ -1,49 +0,0 @@ -module.exports = { - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async up(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.addColumn( - 'messages', - 'createdat', - { - type: Sequelize.DataTypes.DATE, - }, - { transaction }, - ); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, - /** - * @param {QueryInterface} queryInterface - * @param {Sequelize} Sequelize - * @returns {Promise} - */ - async down(queryInterface, Sequelize) { - /** - * @type {Transaction} - */ - const transaction = await queryInterface.sequelize.transaction(); - try { - await queryInterface.removeColumn('messages', 'createdat', { - transaction, - }); - - await transaction.commit(); - } catch (err) { - await transaction.rollback(); - throw err; - } - }, -}; diff --git a/backend/src/db/models/conversations.js b/backend/src/db/models/conversations.js deleted file mode 100644 index a674b01..0000000 --- a/backend/src/db/models/conversations.js +++ /dev/null @@ -1,65 +0,0 @@ -const config = require('../../config'); -const providers = config.providers; -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -module.exports = function (sequelize, DataTypes) { - const conversations = sequelize.define( - 'conversations', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - title: { - type: DataTypes.TEXT, - }, - - createdat: { - type: DataTypes.DATE, - }, - - updatedat: { - type: DataTypes.DATE, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - conversations.associate = (db) => { - /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - //end loop - - db.conversations.belongsTo(db.users, { - as: 'user', - foreignKey: { - name: 'userId', - }, - constraints: false, - }); - - db.conversations.belongsTo(db.users, { - as: 'createdBy', - }); - - db.conversations.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return conversations; -}; diff --git a/backend/src/db/models/messages.js b/backend/src/db/models/messages.js deleted file mode 100644 index ae06c62..0000000 --- a/backend/src/db/models/messages.js +++ /dev/null @@ -1,69 +0,0 @@ -const config = require('../../config'); -const providers = config.providers; -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -module.exports = function (sequelize, DataTypes) { - const messages = sequelize.define( - 'messages', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - content: { - type: DataTypes.TEXT, - }, - - sender: { - type: DataTypes.TEXT, - }, - - agentname: { - type: DataTypes.TEXT, - }, - - createdat: { - type: DataTypes.DATE, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - messages.associate = (db) => { - /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - //end loop - - db.messages.belongsTo(db.conversations, { - as: 'conversation', - foreignKey: { - name: 'conversationId', - }, - constraints: false, - }); - - db.messages.belongsTo(db.users, { - as: 'createdBy', - }); - - db.messages.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return messages; -}; diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index bd41020..1e7803b 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -86,15 +86,7 @@ module.exports = { ]; } - const entities = [ - 'users', - 'agents', - 'roles', - 'permissions', - 'conversations', - 'messages', - , - ]; + const entities = ['users', 'agents', 'roles', 'permissions', ,]; await queryInterface.bulkInsert( 'permissions', entities.flatMap(createPermissions), @@ -416,56 +408,6 @@ primary key ("roles_permissionsId", "permissionId") permissionId: getId('DELETE_PERMISSIONS'), }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_CONVERSATIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_CONVERSATIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_CONVERSATIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_CONVERSATIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_MESSAGES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_MESSAGES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_MESSAGES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_MESSAGES'), - }, - { createdAt, updatedAt, diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 9191104..fb0f48e 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -3,10 +3,6 @@ const Users = db.users; const Agents = db.agents; -const Conversations = db.conversations; - -const Messages = db.messages; - const AgentsData = [ { name: 'M&A Structuring Specialist AI', @@ -15,7 +11,7 @@ const AgentsData = [ purpose: 'Assist in structuring M&A deals', - status: 'development', + status: 'inactive', }, { @@ -25,7 +21,7 @@ const AgentsData = [ purpose: 'Analyze capital market trends', - status: 'active', + status: 'development', }, { @@ -35,7 +31,7 @@ const AgentsData = [ purpose: 'Ensure compliance with regulations', - status: 'inactive', + status: 'active', }, { @@ -45,218 +41,22 @@ const AgentsData = [ purpose: 'Provide insights into customer behavior', - status: 'development', - }, -]; - -const ConversationsData = [ - { - title: 'Jean Piaget', - - // type code here for "relation_one" field - - createdat: new Date(Date.now()), - - updatedat: new Date(Date.now()), - }, - - { - title: 'Frederick Gowland Hopkins', - - // type code here for "relation_one" field - - createdat: new Date(Date.now()), - - updatedat: new Date(Date.now()), - }, - - { - title: 'Louis Pasteur', - - // type code here for "relation_one" field - - createdat: new Date(Date.now()), - - updatedat: new Date(Date.now()), - }, - - { - title: 'Albert Einstein', - - // type code here for "relation_one" field - - createdat: new Date(Date.now()), - - updatedat: new Date(Date.now()), - }, -]; - -const MessagesData = [ - { - // type code here for "relation_one" field - - content: 'Albert Einstein', - - sender: 'Antoine Laurent Lavoisier', - - agentname: 'Galileo Galilei', - - createdat: new Date(Date.now()), - }, - - { - // type code here for "relation_one" field - - content: 'Linus Pauling', - - sender: 'Richard Feynman', - - agentname: 'Johannes Kepler', - - createdat: new Date(Date.now()), - }, - - { - // type code here for "relation_one" field - - content: 'Jonas Salk', - - sender: 'Murray Gell-Mann', - - agentname: 'Frederick Gowland Hopkins', - - createdat: new Date(Date.now()), - }, - - { - // type code here for "relation_one" field - - content: 'Hans Bethe', - - sender: 'J. Robert Oppenheimer', - - agentname: 'Ludwig Boltzmann', - - createdat: new Date(Date.now()), + status: 'inactive', }, ]; // Similar logic for "relation_many" -async function associateConversationWithUser() { - const relatedUser0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Conversation0 = await Conversations.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Conversation0?.setUser) { - await Conversation0.setUser(relatedUser0); - } - - const relatedUser1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Conversation1 = await Conversations.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Conversation1?.setUser) { - await Conversation1.setUser(relatedUser1); - } - - const relatedUser2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Conversation2 = await Conversations.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Conversation2?.setUser) { - await Conversation2.setUser(relatedUser2); - } - - const relatedUser3 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Conversation3 = await Conversations.findOne({ - order: [['id', 'ASC']], - offset: 3, - }); - if (Conversation3?.setUser) { - await Conversation3.setUser(relatedUser3); - } -} - -async function associateMessageWithConversation() { - const relatedConversation0 = await Conversations.findOne({ - offset: Math.floor(Math.random() * (await Conversations.count())), - }); - const Message0 = await Messages.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Message0?.setConversation) { - await Message0.setConversation(relatedConversation0); - } - - const relatedConversation1 = await Conversations.findOne({ - offset: Math.floor(Math.random() * (await Conversations.count())), - }); - const Message1 = await Messages.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Message1?.setConversation) { - await Message1.setConversation(relatedConversation1); - } - - const relatedConversation2 = await Conversations.findOne({ - offset: Math.floor(Math.random() * (await Conversations.count())), - }); - const Message2 = await Messages.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Message2?.setConversation) { - await Message2.setConversation(relatedConversation2); - } - - const relatedConversation3 = await Conversations.findOne({ - offset: Math.floor(Math.random() * (await Conversations.count())), - }); - const Message3 = await Messages.findOne({ - order: [['id', 'ASC']], - offset: 3, - }); - if (Message3?.setConversation) { - await Message3.setConversation(relatedConversation3); - } -} - module.exports = { up: async (queryInterface, Sequelize) => { await Agents.bulkCreate(AgentsData); - await Conversations.bulkCreate(ConversationsData); - - await Messages.bulkCreate(MessagesData); - await Promise.all([ // Similar logic for "relation_many" - - await associateConversationWithUser(), - - await associateMessageWithConversation(), ]); }, down: async (queryInterface, Sequelize) => { await queryInterface.bulkDelete('agents', null, {}); - - await queryInterface.bulkDelete('conversations', null, {}); - - await queryInterface.bulkDelete('messages', null, {}); }, }; diff --git a/backend/src/db/seeders/20250502081336.js b/backend/src/db/seeders/20250502081336.js deleted file mode 100644 index 2133d5d..0000000 --- a/backend/src/db/seeders/20250502081336.js +++ /dev/null @@ -1,87 +0,0 @@ -const { v4: uuid } = require('uuid'); -const db = require('../models'); -const Sequelize = require('sequelize'); -const config = require('../../config'); - -module.exports = { - /** - * @param{import("sequelize").QueryInterface} queryInterface - * @return {Promise} - */ - async up(queryInterface) { - const createdAt = new Date(); - const updatedAt = new Date(); - - /** @type {Map} */ - const idMap = new Map(); - - /** - * @param {string} key - * @return {string} - */ - function getId(key) { - if (idMap.has(key)) { - return idMap.get(key); - } - const id = uuid(); - idMap.set(key, id); - return id; - } - - /** - * @param {string} name - */ - function createPermissions(name) { - return [ - { - id: getId(`CREATE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `CREATE_${name.toUpperCase()}`, - }, - { - id: getId(`READ_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `READ_${name.toUpperCase()}`, - }, - { - id: getId(`UPDATE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `UPDATE_${name.toUpperCase()}`, - }, - { - id: getId(`DELETE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `DELETE_${name.toUpperCase()}`, - }, - ]; - } - - const entities = ['conversations']; - - const createdPermissions = entities.flatMap(createPermissions); - - // Add permissions to database - await queryInterface.bulkInsert('permissions', createdPermissions); - // Get permissions ids - const permissionsIds = createdPermissions.map((p) => p.id); - // Get admin role - const adminRole = await db.roles.findOne({ - where: { name: config.roles.admin }, - }); - - if (adminRole) { - // Add permissions to admin role if it exists - await adminRole.addPermissions(permissionsIds); - } - }, - down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete( - 'permissions', - entities.flatMap(createPermissions), - ); - }, -}; diff --git a/backend/src/db/seeders/20250502081630.js b/backend/src/db/seeders/20250502081630.js deleted file mode 100644 index be900db..0000000 --- a/backend/src/db/seeders/20250502081630.js +++ /dev/null @@ -1,87 +0,0 @@ -const { v4: uuid } = require('uuid'); -const db = require('../models'); -const Sequelize = require('sequelize'); -const config = require('../../config'); - -module.exports = { - /** - * @param{import("sequelize").QueryInterface} queryInterface - * @return {Promise} - */ - async up(queryInterface) { - const createdAt = new Date(); - const updatedAt = new Date(); - - /** @type {Map} */ - const idMap = new Map(); - - /** - * @param {string} key - * @return {string} - */ - function getId(key) { - if (idMap.has(key)) { - return idMap.get(key); - } - const id = uuid(); - idMap.set(key, id); - return id; - } - - /** - * @param {string} name - */ - function createPermissions(name) { - return [ - { - id: getId(`CREATE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `CREATE_${name.toUpperCase()}`, - }, - { - id: getId(`READ_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `READ_${name.toUpperCase()}`, - }, - { - id: getId(`UPDATE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `UPDATE_${name.toUpperCase()}`, - }, - { - id: getId(`DELETE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `DELETE_${name.toUpperCase()}`, - }, - ]; - } - - const entities = ['messages']; - - const createdPermissions = entities.flatMap(createPermissions); - - // Add permissions to database - await queryInterface.bulkInsert('permissions', createdPermissions); - // Get permissions ids - const permissionsIds = createdPermissions.map((p) => p.id); - // Get admin role - const adminRole = await db.roles.findOne({ - where: { name: config.roles.admin }, - }); - - if (adminRole) { - // Add permissions to admin role if it exists - await adminRole.addPermissions(permissionsIds); - } - }, - down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete( - 'permissions', - entities.flatMap(createPermissions), - ); - }, -}; diff --git a/backend/src/index.js b/backend/src/index.js index ca189f5..6b16ef0 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -25,10 +25,6 @@ const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); -const conversationsRoutes = require('./routes/conversations'); - -const messagesRoutes = require('./routes/messages'); - const getBaseUrl = (url) => { if (!url) return ''; return url.endsWith('/api') ? url.slice(0, -4) : url; @@ -118,18 +114,6 @@ app.use( permissionsRoutes, ); -app.use( - '/api/conversations', - passport.authenticate('jwt', { session: false }), - conversationsRoutes, -); - -app.use( - '/api/messages', - passport.authenticate('jwt', { session: false }), - messagesRoutes, -); - app.use( '/api/openai', passport.authenticate('jwt', { session: false }), diff --git a/backend/src/routes/conversations.js b/backend/src/routes/conversations.js deleted file mode 100644 index a8bb0a7..0000000 --- a/backend/src/routes/conversations.js +++ /dev/null @@ -1,444 +0,0 @@ -const express = require('express'); - -const ConversationsService = require('../services/conversations'); -const ConversationsDBApi = require('../db/api/conversations'); -const wrapAsync = require('../helpers').wrapAsync; - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('conversations')); - -/** - * @swagger - * components: - * schemas: - * Conversations: - * type: object - * properties: - - * title: - * type: string - * default: title - - */ - -/** - * @swagger - * tags: - * name: Conversations - * description: The Conversations managing API - */ - -/** - * @swagger - * /api/conversations: - * post: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Add new item - * description: Add new item - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Conversations" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversations" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - */ -router.post( - '/', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await ConversationsService.create( - req.body.data, - req.currentUser, - true, - link.host, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Conversations" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversations" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await ConversationsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversations/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Conversations" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversations" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put( - '/:id', - wrapAsync(async (req, res) => { - await ConversationsService.update( - req.body.data, - req.body.id, - req.currentUser, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversations/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversations" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await ConversationsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversations/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversations" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await ConversationsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversations: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Get all conversations - * description: Get all conversations - * responses: - * 200: - * description: Conversations list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Conversations" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const currentUser = req.currentUser; - const payload = await ConversationsDBApi.findAll(req.query, { - currentUser, - }); - if (filetype && filetype === 'csv') { - const fields = ['id', 'title', 'createdat', 'updatedat']; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - }), -); - -/** - * @swagger - * /api/conversations/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Count all conversations - * description: Count all conversations - * responses: - * 200: - * description: Conversations count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Conversations" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/count', - wrapAsync(async (req, res) => { - const currentUser = req.currentUser; - const payload = await ConversationsDBApi.findAll(req.query, null, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversations/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Find all conversations that match search criteria - * description: Find all conversations that match search criteria - * responses: - * 200: - * description: Conversations list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Conversations" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const payload = await ConversationsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/conversations/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversations] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversations" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await ConversationsDBApi.findBy({ id: req.params.id }); - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/messages.js b/backend/src/routes/messages.js deleted file mode 100644 index adab13b..0000000 --- a/backend/src/routes/messages.js +++ /dev/null @@ -1,450 +0,0 @@ -const express = require('express'); - -const MessagesService = require('../services/messages'); -const MessagesDBApi = require('../db/api/messages'); -const wrapAsync = require('../helpers').wrapAsync; - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('messages')); - -/** - * @swagger - * components: - * schemas: - * Messages: - * type: object - * properties: - - * content: - * type: string - * default: content - * sender: - * type: string - * default: sender - * agentname: - * type: string - * default: agentname - - */ - -/** - * @swagger - * tags: - * name: Messages - * description: The Messages managing API - */ - -/** - * @swagger - * /api/messages: - * post: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Add new item - * description: Add new item - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Messages" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Messages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - */ -router.post( - '/', - wrapAsync(async (req, res) => { - // Save user message - const userMessage = await MessagesService.create( - req.body.data, - req.currentUser, - ); - // Create placeholder agent response - const placeholderResponse = { - conversation: req.body.data.conversation, - content: 'This is a placeholder response from the AI Agent Hub.', - sender: 'agent', - agentName: null, - }; - const agentMessage = await MessagesService.create( - placeholderResponse, - req.currentUser, - ); - // Return both messages - res.status(200).send({ userMessage, agentMessage }); - }), -); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Messages" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Messages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await MessagesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/messages/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Messages" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Messages" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put( - '/:id', - wrapAsync(async (req, res) => { - await MessagesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/messages/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Messages" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await MessagesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/messages/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Messages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await MessagesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/messages: - * get: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Get all messages - * description: Get all messages - * responses: - * 200: - * description: Messages list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Messages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const currentUser = req.currentUser; - const payload = await MessagesDBApi.findAll(req.query, { currentUser }); - if (filetype && filetype === 'csv') { - const fields = ['id', 'content', 'sender', 'agentname', 'createdat']; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - }), -); - -/** - * @swagger - * /api/messages/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Count all messages - * description: Count all messages - * responses: - * 200: - * description: Messages count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Messages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/count', - wrapAsync(async (req, res) => { - const currentUser = req.currentUser; - const payload = await MessagesDBApi.findAll(req.query, null, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/messages/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Find all messages that match search criteria - * description: Find all messages that match search criteria - * responses: - * 200: - * description: Messages list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Messages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const payload = await MessagesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/messages/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Messages] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Messages" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await MessagesDBApi.findBy({ id: req.params.id }); - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/services/conversations.js b/backend/src/services/conversations.js deleted file mode 100644 index 5a67c76..0000000 --- a/backend/src/services/conversations.js +++ /dev/null @@ -1,117 +0,0 @@ -const db = require('../db/models'); -const ConversationsDBApi = require('../db/api/conversations'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); -const stream = require('stream'); - -module.exports = class ConversationsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await ConversationsDBApi.create(data, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async bulkImport(req, res, sendInvitationEmails = true, host) { - const transaction = await db.sequelize.transaction(); - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', async () => { - console.log('CSV results', results); - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - await ConversationsDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - let conversations = await ConversationsDBApi.findBy( - { id }, - { transaction }, - ); - - if (!conversations) { - throw new ValidationError('conversationsNotFound'); - } - - const updatedConversations = await ConversationsDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedConversations; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await ConversationsDBApi.deleteByIds(ids, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await ConversationsDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/backend/src/services/messages.js b/backend/src/services/messages.js deleted file mode 100644 index 9571999..0000000 --- a/backend/src/services/messages.js +++ /dev/null @@ -1,114 +0,0 @@ -const db = require('../db/models'); -const MessagesDBApi = require('../db/api/messages'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); -const stream = require('stream'); - -module.exports = class MessagesService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - const record = await MessagesDBApi.create(data, { - currentUser, - transaction, - }); - await transaction.commit(); - return record; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async bulkImport(req, res, sendInvitationEmails = true, host) { - const transaction = await db.sequelize.transaction(); - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', async () => { - console.log('CSV results', results); - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - await MessagesDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - let messages = await MessagesDBApi.findBy({ id }, { transaction }); - - if (!messages) { - throw new ValidationError('messagesNotFound'); - } - - const updatedMessages = await MessagesDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedMessages; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await MessagesDBApi.deleteByIds(ids, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await MessagesDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/backend/src/services/search.js b/backend/src/services/search.js index b70a22f..0db6137 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -44,10 +44,6 @@ module.exports = class SearchService { users: ['firstName', 'lastName', 'phoneNumber', 'email'], agents: ['name', 'expertise', 'purpose'], - - conversations: ['title'], - - messages: ['content', 'sender', 'agentname'], }; const columnsInt = {}; diff --git a/frontend/json/runtimeError.json b/frontend/json/runtimeError.json deleted file mode 100644 index 9e26dfe..0000000 --- a/frontend/json/runtimeError.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/frontend/src/components/Conversations/CardConversations.tsx b/frontend/src/components/Conversations/CardConversations.tsx deleted file mode 100644 index 657fd7f..0000000 --- a/frontend/src/components/Conversations/CardConversations.tsx +++ /dev/null @@ -1,139 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - conversations: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardConversations = ({ - conversations, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission( - currentUser, - 'UPDATE_CONVERSATIONS', - ); - - return ( -
- {loading && } -
    - {!loading && - conversations.map((item, index) => ( -
  • -
    - - {item.title} - - -
    - -
    -
    -
    -
    -
    Title
    -
    -
    {item.title}
    -
    -
    - -
    -
    User
    -
    -
    - {dataFormatter.usersOneListFormatter(item.user)} -
    -
    -
    - -
    -
    - Createdat -
    -
    -
    - {dataFormatter.dateTimeFormatter(item.createdat)} -
    -
    -
    - -
    -
    - Updatedat -
    -
    -
    - {dataFormatter.dateTimeFormatter(item.updatedat)} -
    -
    -
    -
    -
  • - ))} - {!loading && conversations.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardConversations; diff --git a/frontend/src/components/Conversations/ListConversations.tsx b/frontend/src/components/Conversations/ListConversations.tsx deleted file mode 100644 index 4d395fe..0000000 --- a/frontend/src/components/Conversations/ListConversations.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import { saveFile } from '../../helpers/fileSaver'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import { Pagination } from '../Pagination'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - conversations: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListConversations = ({ - conversations, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission( - currentUser, - 'UPDATE_CONVERSATIONS', - ); - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - return ( - <> -
- {loading && } - {!loading && - conversations.map((item) => ( - -
- dark:divide-dark-700 overflow-x-auto' - } - > -
-

Title

-

{item.title}

-
- -
-

User

-

- {dataFormatter.usersOneListFormatter(item.user)} -

-
- -
-

Createdat

-

- {dataFormatter.dateTimeFormatter(item.createdat)} -

-
- -
-

Updatedat

-

- {dataFormatter.dateTimeFormatter(item.updatedat)} -

-
- - -
-
- ))} - {!loading && conversations.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListConversations; diff --git a/frontend/src/components/Conversations/TableConversations.tsx b/frontend/src/components/Conversations/TableConversations.tsx deleted file mode 100644 index f9bf8f7..0000000 --- a/frontend/src/components/Conversations/TableConversations.tsx +++ /dev/null @@ -1,500 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import CardBox from '../CardBox'; -import { - fetch, - update, - deleteItem, - setRefetch, - deleteItemsByIds, -} from '../../stores/conversations/conversationsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { Field, Form, Formik } from 'formik'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { loadColumns } from './configureConversationsCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -import ListConversations from './ListConversations'; - -const perPage = 10; - -const TableSampleConversations = ({ - filterItems, - setFilterItems, - filters, - showGrid, -}) => { - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { - conversations, - loading, - count, - notify: conversationsNotify, - refetch, - } = useAppSelector((state) => state.conversations); - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = - Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (conversationsNotify.showNotification) { - notify( - conversationsNotify.typeNotification, - conversationsNotify.textNotification, - ); - } - }, [conversationsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false); - const [isModalTrashActive, setIsModalTrashActive] = useState(false); - - const handleModalAction = () => { - setIsModalInfoActive(false); - setIsModalTrashActive(false); - }; - - const handleDeleteModalAction = (id: string) => { - setId(id); - setIsModalTrashActive(true); - }; - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } }; - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - useEffect(() => { - if (!currentUser) return; - - loadColumns(handleDeleteModalAction, `conversations`, currentUser).then( - (newCols) => setColumns(newCols), - ); - }, [currentUser]); - - const handleTableSubmit = async (id: string, data) => { - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - const dataGrid = ( -
- `datagrid--row`} - rows={conversations ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids); - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ); - - return ( - <> - {filterItems && Array.isArray(filterItems) && filterItems.length ? ( - - null} - > -
- <> - {filterItems && - filterItems.map((filterItem) => { - return ( -
-
-
- Filter -
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find( - (filter) => - filter.title === filterItem?.fields?.selectedField, - )?.type === 'enum' ? ( -
-
Value
- - - {filters - .find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - ) - ?.options?.map((option) => ( - - ))} - -
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.number ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.date ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : ( -
-
- Contains -
- -
- )} -
-
- Action -
- { - deleteFilter(filterItem.id); - }} - /> -
-
- ); - })} -
- - -
- -
-
-
- ) : null} - -

Are you sure you want to delete this item?

-
- - {conversations && Array.isArray(conversations) && !showGrid && ( - - )} - - {showGrid && dataGrid} - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ); -}; - -export default TableSampleConversations; diff --git a/frontend/src/components/Conversations/configureConversationsCols.tsx b/frontend/src/components/Conversations/configureConversationsCols.tsx deleted file mode 100644 index 99499a9..0000000 --- a/frontend/src/components/Conversations/configureConversationsCols.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user, -) => { - async function callOptionsApi(entityName: string) { - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_CONVERSATIONS'); - - return [ - { - field: 'title', - headerName: 'Title', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'user', - headerName: 'User', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('users'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - }, - - { - field: 'createdat', - headerName: 'Createdat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.createdat), - }, - - { - field: 'updatedat', - headerName: 'Updatedat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.updatedat), - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ - , - ]; - }, - }, - ]; -}; diff --git a/frontend/src/components/Messages/CardMessages.tsx b/frontend/src/components/Messages/CardMessages.tsx deleted file mode 100644 index d10417f..0000000 --- a/frontend/src/components/Messages/CardMessages.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - messages: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardMessages = ({ - messages, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const asideScrollbarsStyle = useAppSelector( - (state) => state.style.asideScrollbarsStyle, - ); - const bgColor = useAppSelector((state) => state.style.cardsColor); - const darkMode = useAppSelector((state) => state.style.darkMode); - const corners = useAppSelector((state) => state.style.corners); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_MESSAGES'); - - return ( -
- {loading && } -
    - {!loading && - messages.map((item, index) => ( -
  • -
    - - {item.sender} - - -
    - -
    -
    -
    -
    -
    - Conversation -
    -
    -
    - {dataFormatter.conversationsOneListFormatter( - item.conversation, - )} -
    -
    -
    - -
    -
    - Content -
    -
    -
    - {item.content} -
    -
    -
    - -
    -
    - Sender -
    -
    -
    - {item.sender} -
    -
    -
    - -
    -
    - Agentname -
    -
    -
    - {item.agentname} -
    -
    -
    - -
    -
    - Createdat -
    -
    -
    - {dataFormatter.dateTimeFormatter(item.createdat)} -
    -
    -
    -
    -
  • - ))} - {!loading && messages.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardMessages; diff --git a/frontend/src/components/Messages/ListMessages.tsx b/frontend/src/components/Messages/ListMessages.tsx deleted file mode 100644 index e3fb075..0000000 --- a/frontend/src/components/Messages/ListMessages.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import { saveFile } from '../../helpers/fileSaver'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import { Pagination } from '../Pagination'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - messages: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListMessages = ({ - messages, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_MESSAGES'); - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - return ( - <> -
- {loading && } - {!loading && - messages.map((item) => ( - -
- dark:divide-dark-700 overflow-x-auto' - } - > -
-

Conversation

-

- {dataFormatter.conversationsOneListFormatter( - item.conversation, - )} -

-
- -
-

Content

-

{item.content}

-
- -
-

Sender

-

{item.sender}

-
- -
-

Agentname

-

{item.agentname}

-
- -
-

Createdat

-

- {dataFormatter.dateTimeFormatter(item.createdat)} -

-
- - -
-
- ))} - {!loading && messages.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListMessages; diff --git a/frontend/src/components/Messages/TableMessages.tsx b/frontend/src/components/Messages/TableMessages.tsx deleted file mode 100644 index 5a88970..0000000 --- a/frontend/src/components/Messages/TableMessages.tsx +++ /dev/null @@ -1,484 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import CardBox from '../CardBox'; -import { - fetch, - update, - deleteItem, - setRefetch, - deleteItemsByIds, -} from '../../stores/messages/messagesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { Field, Form, Formik } from 'formik'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { loadColumns } from './configureMessagesCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -const perPage = 10; - -const TableSampleMessages = ({ - filterItems, - setFilterItems, - filters, - showGrid, -}) => { - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { - messages, - loading, - count, - notify: messagesNotify, - refetch, - } = useAppSelector((state) => state.messages); - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = - Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (messagesNotify.showNotification) { - notify(messagesNotify.typeNotification, messagesNotify.textNotification); - } - }, [messagesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false); - const [isModalTrashActive, setIsModalTrashActive] = useState(false); - - const handleModalAction = () => { - setIsModalInfoActive(false); - setIsModalTrashActive(false); - }; - - const handleDeleteModalAction = (id: string) => { - setId(id); - setIsModalTrashActive(true); - }; - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } }; - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - useEffect(() => { - if (!currentUser) return; - - loadColumns(handleDeleteModalAction, `messages`, currentUser).then( - (newCols) => setColumns(newCols), - ); - }, [currentUser]); - - const handleTableSubmit = async (id: string, data) => { - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - const dataGrid = ( -
- `datagrid--row`} - rows={messages ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids); - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ); - - return ( - <> - {filterItems && Array.isArray(filterItems) && filterItems.length ? ( - - null} - > -
- <> - {filterItems && - filterItems.map((filterItem) => { - return ( -
-
-
- Filter -
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find( - (filter) => - filter.title === filterItem?.fields?.selectedField, - )?.type === 'enum' ? ( -
-
Value
- - - {filters - .find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - ) - ?.options?.map((option) => ( - - ))} - -
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.number ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.date ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : ( -
-
- Contains -
- -
- )} -
-
- Action -
- { - deleteFilter(filterItem.id); - }} - /> -
-
- ); - })} -
- - -
- -
-
-
- ) : null} - -

Are you sure you want to delete this item?

-
- - {dataGrid} - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ); -}; - -export default TableSampleMessages; diff --git a/frontend/src/components/Messages/configureMessagesCols.tsx b/frontend/src/components/Messages/configureMessagesCols.tsx deleted file mode 100644 index fb571d6..0000000 --- a/frontend/src/components/Messages/configureMessagesCols.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user, -) => { - async function callOptionsApi(entityName: string) { - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_MESSAGES'); - - return [ - { - field: 'conversation', - headerName: 'Conversation', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('conversations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - }, - - { - field: 'content', - headerName: 'Content', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'sender', - headerName: 'Sender', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'agentname', - headerName: 'Agentname', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'createdat', - headerName: 'Createdat', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.createdat), - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ - , - ]; - }, - }, - ]; -}; diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 7a700e4..1fa85f7 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -43,22 +43,6 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, permissions: 'READ_PERMISSIONS', }, - { - href: '/conversations/conversations-list', - label: 'Conversations', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CONVERSATIONS', - }, - { - href: '/messages/messages-list', - label: 'Messages', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_MESSAGES', - }, { href: '/profile', label: 'Profile', diff --git a/frontend/src/pages/conversations/[conversationsId].tsx b/frontend/src/pages/conversations/[conversationsId].tsx deleted file mode 100644 index 75ba77e..0000000 --- a/frontend/src/pages/conversations/[conversationsId].tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/conversations/conversationsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; - -const EditConversations = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - title: '', - - user: null, - - createdat: new Date(), - - updatedat: new Date(), - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { conversations } = useAppSelector((state) => state.conversations); - - const { conversationsId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: conversationsId })); - }, [conversationsId]); - - useEffect(() => { - if (typeof conversations === 'object') { - setInitialValues(conversations); - } - }, [conversations]); - - useEffect(() => { - if (typeof conversations === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = conversations[el]), - ); - - setInitialValues(newInitialVal); - } - }, [conversations]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: conversationsId, data })); - await router.push('/conversations/conversations-list'); - }; - - return ( - <> - - {getPageTitle('Edit conversations')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - setInitialValues({ ...initialValues, createdat: date }) - } - /> - - - - - setInitialValues({ ...initialValues, updatedat: date }) - } - /> - - - - - - - - router.push('/conversations/conversations-list') - } - /> - - -
-
-
- - ); -}; - -EditConversations.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditConversations; diff --git a/frontend/src/pages/conversations/conversations-edit.tsx b/frontend/src/pages/conversations/conversations-edit.tsx deleted file mode 100644 index 740f644..0000000 --- a/frontend/src/pages/conversations/conversations-edit.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/conversations/conversationsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; - -const EditConversationsPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - title: '', - - user: null, - - createdat: new Date(), - - updatedat: new Date(), - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { conversations } = useAppSelector((state) => state.conversations); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof conversations === 'object') { - setInitialValues(conversations); - } - }, [conversations]); - - useEffect(() => { - if (typeof conversations === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = conversations[el]), - ); - setInitialValues(newInitialVal); - } - }, [conversations]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/conversations/conversations-list'); - }; - - return ( - <> - - {getPageTitle('Edit conversations')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - setInitialValues({ ...initialValues, createdat: date }) - } - /> - - - - - setInitialValues({ ...initialValues, updatedat: date }) - } - /> - - - - - - - - router.push('/conversations/conversations-list') - } - /> - - -
-
-
- - ); -}; - -EditConversationsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditConversationsPage; diff --git a/frontend/src/pages/conversations/conversations-list.tsx b/frontend/src/pages/conversations/conversations-list.tsx deleted file mode 100644 index 76dd1d3..0000000 --- a/frontend/src/pages/conversations/conversations-list.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableConversations from '../../components/Conversations/TableConversations'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; -import { - setRefetch, - uploadCsv, -} from '../../stores/conversations/conversationsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const ConversationsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'Title', title: 'title' }, - - { label: 'Createdat', title: 'createdat', date: 'true' }, - { label: 'Updatedat', title: 'updatedat', date: 'true' }, - - { label: 'User', title: 'user' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_CONVERSATIONS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getConversationsCSV = async () => { - const response = await axios({ - url: '/conversations?filetype=csv', - method: 'GET', - responseType: 'blob', - }); - const type = response.headers['content-type']; - const blob = new Blob([response.data], { type: type }); - const link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = 'conversationsCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Conversations')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
- -
- - Switch to Table - -
-
- - - - -
- - - - - ); -}; - -ConversationsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversationsTablesPage; diff --git a/frontend/src/pages/conversations/conversations-new.tsx b/frontend/src/pages/conversations/conversations-new.tsx deleted file mode 100644 index 4d047b2..0000000 --- a/frontend/src/pages/conversations/conversations-new.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { - mdiAccount, - mdiChartTimelineVariant, - mdiMail, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SwitchField } from '../../components/SwitchField'; - -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { RichTextField } from '../../components/RichTextField'; - -import { create } from '../../stores/conversations/conversationsSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - title: '', - - user: '', - - createdat: '', - - updatedat: '', -}; - -const ConversationsNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/conversations/conversations-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - router.push('/conversations/conversations-list') - } - /> - - -
-
-
- - ); -}; - -ConversationsNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversationsNew; diff --git a/frontend/src/pages/conversations/conversations-table.tsx b/frontend/src/pages/conversations/conversations-table.tsx deleted file mode 100644 index 091f3be..0000000 --- a/frontend/src/pages/conversations/conversations-table.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableConversations from '../../components/Conversations/TableConversations'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; -import { - setRefetch, - uploadCsv, -} from '../../stores/conversations/conversationsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const ConversationsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'Title', title: 'title' }, - - { label: 'Createdat', title: 'createdat', date: 'true' }, - { label: 'Updatedat', title: 'updatedat', date: 'true' }, - - { label: 'User', title: 'user' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_CONVERSATIONS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getConversationsCSV = async () => { - const response = await axios({ - url: '/conversations?filetype=csv', - method: 'GET', - responseType: 'blob', - }); - const type = response.headers['content-type']; - const blob = new Blob([response.data], { type: type }); - const link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = 'conversationsCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Conversations')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
- - - Back to list - -
-
- - - -
- - - - - ); -}; - -ConversationsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversationsTablesPage; diff --git a/frontend/src/pages/conversations/conversations-view.tsx b/frontend/src/pages/conversations/conversations-view.tsx deleted file mode 100644 index 5d58e98..0000000 --- a/frontend/src/pages/conversations/conversations-view.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/conversations/conversationsSlice'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; -import BaseButton from '../../components/BaseButton'; -import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; -import FormField from '../../components/FormField'; - -const ConversationsView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { conversations } = useAppSelector((state) => state.conversations); - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str, `str`); - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - return ( - <> - - {getPageTitle('View conversations')} - - - - - - -
-

Title

-

{conversations?.title}

-
- -
-

User

- -

{conversations?.user?.firstName ?? 'No data'}

-
- - - {conversations.createdat ? ( - - ) : ( -

No Createdat

- )} -
- - - {conversations.updatedat ? ( - - ) : ( -

No Updatedat

- )} -
- - <> -

Messages Conversation

- -
- - - - - - - - - - - - - - {conversations.messages_conversation && - Array.isArray(conversations.messages_conversation) && - conversations.messages_conversation.map((item: any) => ( - - router.push( - `/messages/messages-view/?id=${item.id}`, - ) - } - > - - - - - - - - - ))} - -
ContentSenderAgentnameCreatedat
{item.content}{item.sender}{item.agentname} - {dataFormatter.dateTimeFormatter(item.createdat)} -
-
- {!conversations?.messages_conversation?.length && ( -
No data
- )} -
- - - - - router.push('/conversations/conversations-list')} - /> -
-
- - ); -}; - -ConversationsView.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversationsView; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 008df0b..a4cd852 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -26,8 +26,6 @@ const Dashboard = () => { const [agents, setAgents] = React.useState('Loading...'); const [roles, setRoles] = React.useState('Loading...'); const [permissions, setPermissions] = React.useState('Loading...'); - const [conversations, setConversations] = React.useState('Loading...'); - const [messages, setMessages] = React.useState('Loading...'); const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' }, @@ -38,22 +36,8 @@ const Dashboard = () => { const { rolesWidgets, loading } = useAppSelector((state) => state.roles); async function loadData() { - const entities = [ - 'users', - 'agents', - 'roles', - 'permissions', - 'conversations', - 'messages', - ]; - const fns = [ - setUsers, - setAgents, - setRoles, - setPermissions, - setConversations, - setMessages, - ]; + const entities = ['users', 'agents', 'roles', 'permissions']; + const fns = [setUsers, setAgents, setRoles, setPermissions]; const requests = entities.map((entity, index) => { if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { @@ -294,70 +278,6 @@ const Dashboard = () => { )} - - {hasPermission(currentUser, 'READ_CONVERSATIONS') && ( - -
-
-
-
- Conversations -
-
- {conversations} -
-
-
- -
-
-
- - )} - - {hasPermission(currentUser, 'READ_MESSAGES') && ( - -
-
-
-
- Messages -
-
- {messages} -
-
-
- -
-
-
- - )} diff --git a/frontend/src/pages/messages/[messagesId].tsx b/frontend/src/pages/messages/[messagesId].tsx deleted file mode 100644 index 92e3ef0..0000000 --- a/frontend/src/pages/messages/[messagesId].tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/messages/messagesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; - -const EditMessages = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - conversation: null, - - content: '', - - sender: '', - - agentname: '', - - createdat: new Date(), - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { messages } = useAppSelector((state) => state.messages); - - const { messagesId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: messagesId })); - }, [messagesId]); - - useEffect(() => { - if (typeof messages === 'object') { - setInitialValues(messages); - } - }, [messages]); - - useEffect(() => { - if (typeof messages === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach((el) => (newInitialVal[el] = messages[el])); - - setInitialValues(newInitialVal); - } - }, [messages]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: messagesId, data })); - await router.push('/messages/messages-list'); - }; - - return ( - <> - - {getPageTitle('Edit messages')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - setInitialValues({ ...initialValues, createdat: date }) - } - /> - - - - - - - router.push('/messages/messages-list')} - /> - - -
-
-
- - ); -}; - -EditMessages.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditMessages; diff --git a/frontend/src/pages/messages/messages-edit.tsx b/frontend/src/pages/messages/messages-edit.tsx deleted file mode 100644 index a95b888..0000000 --- a/frontend/src/pages/messages/messages-edit.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/messages/messagesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; - -const EditMessagesPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - conversation: null, - - content: '', - - sender: '', - - agentname: '', - - createdat: new Date(), - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { messages } = useAppSelector((state) => state.messages); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof messages === 'object') { - setInitialValues(messages); - } - }, [messages]); - - useEffect(() => { - if (typeof messages === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach((el) => (newInitialVal[el] = messages[el])); - setInitialValues(newInitialVal); - } - }, [messages]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/messages/messages-list'); - }; - - return ( - <> - - {getPageTitle('Edit messages')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - setInitialValues({ ...initialValues, createdat: date }) - } - /> - - - - - - - router.push('/messages/messages-list')} - /> - - -
-
-
- - ); -}; - -EditMessagesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditMessagesPage; diff --git a/frontend/src/pages/messages/messages-list.tsx b/frontend/src/pages/messages/messages-list.tsx deleted file mode 100644 index 1ef906c..0000000 --- a/frontend/src/pages/messages/messages-list.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableMessages from '../../components/Messages/TableMessages'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; -import { setRefetch, uploadCsv } from '../../stores/messages/messagesSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const MessagesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'Content', title: 'content' }, - { label: 'Sender', title: 'sender' }, - { label: 'Agentname', title: 'agentname' }, - - { label: 'Createdat', title: 'createdat', date: 'true' }, - - { label: 'Conversation', title: 'conversation' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_MESSAGES'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getMessagesCSV = async () => { - const response = await axios({ - url: '/messages?filetype=csv', - method: 'GET', - responseType: 'blob', - }); - const type = response.headers['content-type']; - const blob = new Blob([response.data], { type: type }); - const link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = 'messagesCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Messages')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - - -
- - - - - ); -}; - -MessagesTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default MessagesTablesPage; diff --git a/frontend/src/pages/messages/messages-new.tsx b/frontend/src/pages/messages/messages-new.tsx deleted file mode 100644 index 76abba7..0000000 --- a/frontend/src/pages/messages/messages-new.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { - mdiAccount, - mdiChartTimelineVariant, - mdiMail, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SwitchField } from '../../components/SwitchField'; - -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { RichTextField } from '../../components/RichTextField'; - -import { create } from '../../stores/messages/messagesSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - conversation: '', - - content: '', - - sender: '', - - agentname: '', - - createdat: '', -}; - -const MessagesNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/messages/messages-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - router.push('/messages/messages-list')} - /> - - -
-
-
- - ); -}; - -MessagesNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default MessagesNew; diff --git a/frontend/src/pages/messages/messages-table.tsx b/frontend/src/pages/messages/messages-table.tsx deleted file mode 100644 index 8de31fa..0000000 --- a/frontend/src/pages/messages/messages-table.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableMessages from '../../components/Messages/TableMessages'; -import BaseButton from '../../components/BaseButton'; -import axios from 'axios'; -import Link from 'next/link'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import CardBoxModal from '../../components/CardBoxModal'; -import DragDropFilePicker from '../../components/DragDropFilePicker'; -import { setRefetch, uploadCsv } from '../../stores/messages/messagesSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const MessagesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'Content', title: 'content' }, - { label: 'Sender', title: 'sender' }, - { label: 'Agentname', title: 'agentname' }, - - { label: 'Createdat', title: 'createdat', date: 'true' }, - - { label: 'Conversation', title: 'conversation' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_MESSAGES'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getMessagesCSV = async () => { - const response = await axios({ - url: '/messages?filetype=csv', - method: 'GET', - responseType: 'blob', - }); - const type = response.headers['content-type']; - const blob = new Blob([response.data], { type: type }); - const link = document.createElement('a'); - link.href = window.URL.createObjectURL(blob); - link.download = 'messagesCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Messages')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
- - - Back to table - -
-
- - - -
- - - - - ); -}; - -MessagesTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default MessagesTablesPage; diff --git a/frontend/src/pages/messages/messages-view.tsx b/frontend/src/pages/messages/messages-view.tsx deleted file mode 100644 index 6301eb5..0000000 --- a/frontend/src/pages/messages/messages-view.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/messages/messagesSlice'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; -import BaseButton from '../../components/BaseButton'; -import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; -import FormField from '../../components/FormField'; - -const MessagesView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { messages } = useAppSelector((state) => state.messages); - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str, `str`); - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - return ( - <> - - {getPageTitle('View messages')} - - - - - - -
-

Conversation

- -

{messages?.conversation?.title ?? 'No data'}

-
- -
-

Content

-

{messages?.content}

-
- -
-

Sender

-

{messages?.sender}

-
- -
-

Agentname

-

{messages?.agentname}

-
- - - {messages.createdat ? ( - - ) : ( -

No Createdat

- )} -
- - - - router.push('/messages/messages-list')} - /> -
-
- - ); -}; - -MessagesView.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default MessagesView; diff --git a/frontend/src/pages/users/users-view.tsx b/frontend/src/pages/users/users-view.tsx index 83ca701..ce0851d 100644 --- a/frontend/src/pages/users/users-view.tsx +++ b/frontend/src/pages/users/users-view.tsx @@ -138,55 +138,6 @@ const UsersView = () => { - <> -

Conversations User

- -
- - - - - - - - - - - - {users.conversations_user && - Array.isArray(users.conversations_user) && - users.conversations_user.map((item: any) => ( - - router.push( - `/conversations/conversations-view/?id=${item.id}`, - ) - } - > - - - - - - - ))} - -
TitleCreatedatUpdatedat
{item.title} - {dataFormatter.dateTimeFormatter(item.createdat)} - - {dataFormatter.dateTimeFormatter(item.updatedat)} -
-
- {!users?.conversations_user?.length && ( -
No data
- )} -
- - { - const { id, query } = data; - const result = await axios.get( - `conversations${query || (id ? `/${id}` : '')}`, - ); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; - }, -); - -export const deleteItemsByIds = createAsyncThunk( - 'conversations/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('conversations/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'conversations/deleteConversations', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`conversations/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'conversations/createConversations', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('conversations', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const uploadCsv = createAsyncThunk( - 'conversations/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('conversations/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'conversations/updateConversations', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`conversations/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const conversationsSlice = createSlice({ - name: 'conversations', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.conversations = action.payload.rows; - state.count = action.payload.count; - } else { - state.conversations = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Conversations has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify( - state, - `${'Conversations'.slice(0, -1)} has been deleted`, - ); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify( - state, - `${'Conversations'.slice(0, -1)} has been created`, - ); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify( - state, - `${'Conversations'.slice(0, -1)} has been updated`, - ); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Conversations has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = conversationsSlice.actions; - -export default conversationsSlice.reducer; diff --git a/frontend/src/stores/messages/messagesSlice.ts b/frontend/src/stores/messages/messagesSlice.ts deleted file mode 100644 index a62108a..0000000 --- a/frontend/src/stores/messages/messagesSlice.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; -import axios from 'axios'; -import { - fulfilledNotify, - rejectNotify, - resetNotify, -} from '../../helpers/notifyStateHandler'; - -interface MainState { - messages: any; - loading: boolean; - count: number; - refetch: boolean; - rolesWidgets: any[]; - notify: { - showNotification: boolean; - textNotification: string; - typeNotification: string; - }; -} - -const initialState: MainState = { - messages: [], - loading: false, - count: 0, - refetch: false, - rolesWidgets: [], - notify: { - showNotification: false, - textNotification: '', - typeNotification: 'warn', - }, -}; - -export const fetch = createAsyncThunk('messages/fetch', async (data: any) => { - const { id, query } = data; - const result = await axios.get(`messages${query || (id ? `/${id}` : '')}`); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; -}); - -export const deleteItemsByIds = createAsyncThunk( - 'messages/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('messages/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'messages/deleteMessages', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`messages/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'messages/createMessages', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('messages', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -// Thunk for chat messaging including stub agent response -export const createChatMessage = createAsyncThunk( - 'messages/createChatMessage', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('messages', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - return rejectWithValue(error.response.data); - } - } -); - -export const uploadCsv = createAsyncThunk( - 'messages/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('messages/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'messages/updateMessages', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`messages/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const messagesSlice = createSlice({ - name: 'messages', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.messages = action.payload.rows; - state.count = action.payload.count; - } else { - state.messages = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Messages has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Messages'.slice(0, -1)} has been deleted`); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Messages'.slice(0, -1)} has been created`); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Messages'.slice(0, -1)} has been updated`); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Messages has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = messagesSlice.actions; - -export default messagesSlice.reducer; diff --git a/frontend/src/stores/messages/messagesSlice.ts.temp b/frontend/src/stores/messages/messagesSlice.ts.temp deleted file mode 100644 index ed3c916..0000000 --- a/frontend/src/stores/messages/messagesSlice.ts.temp +++ /dev/null @@ -1,268 +0,0 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; -import axios from 'axios'; -import { - fulfilledNotify, - rejectNotify, - resetNotify, -} from '../../helpers/notifyStateHandler'; - -interface MainState { - messages: any; - loading: boolean; - count: number; - refetch: boolean; - rolesWidgets: any[]; - notify: { - showNotification: boolean; - textNotification: string; - typeNotification: string; - }; -} - -const initialState: MainState = { - messages: [], - loading: false, - count: 0, - refetch: false, - rolesWidgets: [], - notify: { - showNotification: false, - textNotification: '', - typeNotification: 'warn', - }, -}; - -export const fetch = createAsyncThunk('messages/fetch', async (data: any) => { - const { id, query } = data; - const result = await axios.get(`messages${query || (id ? `/${id}` : '')}`); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; -}); - -export const deleteItemsByIds = createAsyncThunk( - 'messages/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('messages/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'messages/deleteMessages', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`messages/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'messages/createMessages', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('messages', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); -// Chat-specific thunk: sends user message and receives stub agent response -export const createChatMessage = createAsyncThunk( - 'messages/createChatMessage', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('messages', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - return rejectWithValue(error.response.data); - } - } -); - - -// Thunk for chat messaging including stub agent response -export const createChatMessage = createAsyncThunk( - 'messages/createChatMessage', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('messages', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - return rejectWithValue(error.response.data); - } - } -); - -export const uploadCsv = createAsyncThunk( - 'messages/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('messages/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'messages/updateMessages', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`messages/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const messagesSlice = createSlice({ - name: 'messages', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.messages = action.payload.rows; - state.count = action.payload.count; - } else { - state.messages = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Messages has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Messages'.slice(0, -1)} has been deleted`); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Messages'.slice(0, -1)} has been created`); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Messages'.slice(0, -1)} has been updated`); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Messages has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = messagesSlice.actions; - -export default messagesSlice.reducer; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index 7ef8a77..c3bc371 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -8,8 +8,6 @@ import usersSlice from './users/usersSlice'; import agentsSlice from './agents/agentsSlice'; import rolesSlice from './roles/rolesSlice'; import permissionsSlice from './permissions/permissionsSlice'; -import conversationsSlice from './conversations/conversationsSlice'; -import messagesSlice from './messages/messagesSlice'; export const store = configureStore({ reducer: { @@ -22,8 +20,6 @@ export const store = configureStore({ agents: agentsSlice, roles: rolesSlice, permissions: permissionsSlice, - conversations: conversationsSlice, - messages: messagesSlice, }, });