diff --git a/backend/README.md b/backend/README.md index 2ccf948..65e68ba 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,5 +1,5 @@ -#Sales Pipeline CRM - template backend, +# Coaching SaaS Workspace - template backend #### Run App on local machine: @@ -30,10 +30,10 @@ - `psql postgres -U admin` - Type this command to creating a new database. - - `postgres=> CREATE DATABASE db_sales_pipeline_crm;` + - `postgres=> CREATE DATABASE db_coaching_saas_workspace;` - Then give that new user privileges to the new database then quit the `psql`. - - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_sales_pipeline_crm TO admin;` + - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_coaching_saas_workspace TO admin;` - `postgres=> \q` ------------ @@ -49,7 +49,7 @@ http://host_name/api-docs ##### Setup database tables or update after schema change - `yarn db:migrate` - ##### Seed the initial data (admin accounts, relevant for the first setup): + ##### Seed the initial data (admin users, roles, and coaching demo data): - `yarn db:seed` ##### Start build: diff --git a/backend/package.json b/backend/package.json index 8d6ab72..daaec10 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { - "name": "salespipelinecrm", - "description": "Sales Pipeline CRM - template backend", + "name": "coaching-saas-workspace", + "description": "Coaching SaaS Workspace - template backend", "scripts": { "start": "npm run db:migrate && npm run db:seed && npm run watch", "lint": "eslint . --ext .js", diff --git a/backend/src/db/api/accounts.js b/backend/src/db/api/accounts.js deleted file mode 100644 index b11fb7a..0000000 --- a/backend/src/db/api/accounts.js +++ /dev/null @@ -1,701 +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 AccountsDBApi { - - - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const accounts = await db.accounts.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - domain: data.domain - || - null - , - - industry: data.industry - || - null - , - - employee_count: data.employee_count - || - null - , - - annual_revenue: data.annual_revenue - || - null - , - - phone: data.phone - || - null - , - - website: data.website - || - null - , - - address: data.address - || - null - , - - account_status: data.account_status - || - null - , - - last_contacted_at: data.last_contacted_at - || - null - , - - next_follow_up_at: data.next_follow_up_at - || - null - , - - notes: data.notes - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - - await accounts.setOwner( data.owner || null, { - transaction, - }); - - - - - - - return accounts; - } - - - 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 accountsData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - domain: item.domain - || - null - , - - industry: item.industry - || - null - , - - employee_count: item.employee_count - || - null - , - - annual_revenue: item.annual_revenue - || - null - , - - phone: item.phone - || - null - , - - website: item.website - || - null - , - - address: item.address - || - null - , - - account_status: item.account_status - || - null - , - - last_contacted_at: item.last_contacted_at - || - null - , - - next_follow_up_at: item.next_follow_up_at - || - null - , - - notes: item.notes - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const accounts = await db.accounts.bulkCreate(accountsData, { transaction }); - - // For each item created, replace relation files - - - return accounts; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const accounts = await db.accounts.findByPk(id, {}, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.domain !== undefined) updatePayload.domain = data.domain; - - - if (data.industry !== undefined) updatePayload.industry = data.industry; - - - if (data.employee_count !== undefined) updatePayload.employee_count = data.employee_count; - - - if (data.annual_revenue !== undefined) updatePayload.annual_revenue = data.annual_revenue; - - - if (data.phone !== undefined) updatePayload.phone = data.phone; - - - if (data.website !== undefined) updatePayload.website = data.website; - - - if (data.address !== undefined) updatePayload.address = data.address; - - - if (data.account_status !== undefined) updatePayload.account_status = data.account_status; - - - if (data.last_contacted_at !== undefined) updatePayload.last_contacted_at = data.last_contacted_at; - - - if (data.next_follow_up_at !== undefined) updatePayload.next_follow_up_at = data.next_follow_up_at; - - - if (data.notes !== undefined) updatePayload.notes = data.notes; - - - updatePayload.updatedById = currentUser.id; - - await accounts.update(updatePayload, {transaction}); - - - - if (data.owner !== undefined) { - await accounts.setOwner( - - data.owner, - - { transaction } - ); - } - - - - - - - - return accounts; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const accounts = await db.accounts.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of accounts) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of accounts) { - await record.destroy({transaction}); - } - }); - - - return accounts; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const accounts = await db.accounts.findByPk(id, options); - - await accounts.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await accounts.destroy({ - transaction - }); - - return accounts; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const accounts = await db.accounts.findOne( - { where }, - { transaction }, - ); - - if (!accounts) { - return accounts; - } - - const output = accounts.get({plain: true}); - - - - - - - output.contacts_account = await accounts.getContacts_account({ - transaction - }); - - - - output.leads_account = await accounts.getLeads_account({ - transaction - }); - - - - output.deals_account = await accounts.getDeals_account({ - transaction - }); - - - output.activities_account = await accounts.getActivities_account({ - transaction - }); - - - - output.owner = await accounts.getOwner({ - 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: 'owner', - - where: filter.owner ? { - [Op.or]: [ - { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } }, - { - firstName: { - [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'accounts', - 'name', - filter.name, - ), - }; - } - - if (filter.domain) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'accounts', - 'domain', - filter.domain, - ), - }; - } - - if (filter.industry) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'accounts', - 'industry', - filter.industry, - ), - }; - } - - if (filter.phone) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'accounts', - 'phone', - filter.phone, - ), - }; - } - - if (filter.website) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'accounts', - 'website', - filter.website, - ), - }; - } - - if (filter.address) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'accounts', - 'address', - filter.address, - ), - }; - } - - if (filter.notes) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'accounts', - 'notes', - filter.notes, - ), - }; - } - - - - - - - if (filter.employee_countRange) { - const [start, end] = filter.employee_countRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - employee_count: { - ...where.employee_count, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - employee_count: { - ...where.employee_count, - [Op.lte]: end, - }, - }; - } - } - - if (filter.annual_revenueRange) { - const [start, end] = filter.annual_revenueRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - annual_revenue: { - ...where.annual_revenue, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - annual_revenue: { - ...where.annual_revenue, - [Op.lte]: end, - }, - }; - } - } - - if (filter.last_contacted_atRange) { - const [start, end] = filter.last_contacted_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - last_contacted_at: { - ...where.last_contacted_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - last_contacted_at: { - ...where.last_contacted_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.next_follow_up_atRange) { - const [start, end] = filter.next_follow_up_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.account_status) { - where = { - ...where, - account_status: filter.account_status, - }; - } - - - - - - - - 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.accounts.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( - 'accounts', - 'name', - query, - ), - ], - }; - } - - const records = await db.accounts.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } - - -}; - diff --git a/backend/src/db/api/activities.js b/backend/src/db/api/activities.js deleted file mode 100644 index f745090..0000000 --- a/backend/src/db/api/activities.js +++ /dev/null @@ -1,776 +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 ActivitiesDBApi { - - - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const activities = await db.activities.create( - { - id: data.id || undefined, - - activity_type: data.activity_type - || - null - , - - subject: data.subject - || - null - , - - details: data.details - || - null - , - - activity_at: data.activity_at - || - null - , - - due_at: data.due_at - || - null - , - - completed_at: data.completed_at - || - null - , - - status: data.status - || - null - , - - priority: data.priority - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - - await activities.setOwner( data.owner || null, { - transaction, - }); - - await activities.setLead( data.lead || null, { - transaction, - }); - - await activities.setDeal( data.deal || null, { - transaction, - }); - - await activities.setContact( data.contact || null, { - transaction, - }); - - await activities.setAccount( data.account || null, { - transaction, - }); - - - - - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.activities.getTableName(), - belongsToColumn: 'attachments', - belongsToId: activities.id, - }, - data.attachments, - options, - ); - - - return activities; - } - - - 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 activitiesData = data.map((item, index) => ({ - id: item.id || undefined, - - activity_type: item.activity_type - || - null - , - - subject: item.subject - || - null - , - - details: item.details - || - null - , - - activity_at: item.activity_at - || - null - , - - due_at: item.due_at - || - null - , - - completed_at: item.completed_at - || - null - , - - status: item.status - || - null - , - - priority: item.priority - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const activities = await db.activities.bulkCreate(activitiesData, { transaction }); - - // For each item created, replace relation files - - for (let i = 0; i < activities.length; i++) { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.activities.getTableName(), - belongsToColumn: 'attachments', - belongsToId: activities[i].id, - }, - data[i].attachments, - options, - ); - } - - - return activities; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const activities = await db.activities.findByPk(id, {}, {transaction}); - - - - - const updatePayload = {}; - - if (data.activity_type !== undefined) updatePayload.activity_type = data.activity_type; - - - if (data.subject !== undefined) updatePayload.subject = data.subject; - - - if (data.details !== undefined) updatePayload.details = data.details; - - - if (data.activity_at !== undefined) updatePayload.activity_at = data.activity_at; - - - if (data.due_at !== undefined) updatePayload.due_at = data.due_at; - - - if (data.completed_at !== undefined) updatePayload.completed_at = data.completed_at; - - - if (data.status !== undefined) updatePayload.status = data.status; - - - if (data.priority !== undefined) updatePayload.priority = data.priority; - - - updatePayload.updatedById = currentUser.id; - - await activities.update(updatePayload, {transaction}); - - - - if (data.owner !== undefined) { - await activities.setOwner( - - data.owner, - - { transaction } - ); - } - - if (data.lead !== undefined) { - await activities.setLead( - - data.lead, - - { transaction } - ); - } - - if (data.deal !== undefined) { - await activities.setDeal( - - data.deal, - - { transaction } - ); - } - - if (data.contact !== undefined) { - await activities.setContact( - - data.contact, - - { transaction } - ); - } - - if (data.account !== undefined) { - await activities.setAccount( - - data.account, - - { transaction } - ); - } - - - - - - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.activities.getTableName(), - belongsToColumn: 'attachments', - belongsToId: activities.id, - }, - data.attachments, - options, - ); - - - return activities; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const activities = await db.activities.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of activities) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of activities) { - await record.destroy({transaction}); - } - }); - - - return activities; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const activities = await db.activities.findByPk(id, options); - - await activities.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await activities.destroy({ - transaction - }); - - return activities; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const activities = await db.activities.findOne( - { where }, - { transaction }, - ); - - if (!activities) { - return activities; - } - - const output = activities.get({plain: true}); - - - - - - - - - - - - - - output.owner = await activities.getOwner({ - transaction - }); - - - output.lead = await activities.getLead({ - transaction - }); - - - output.deal = await activities.getDeal({ - transaction - }); - - - output.contact = await activities.getContact({ - transaction - }); - - - output.account = await activities.getAccount({ - transaction - }); - - - output.attachments = await activities.getAttachments({ - 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: 'owner', - - where: filter.owner ? { - [Op.or]: [ - { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } }, - { - firstName: { - [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.leads, - as: 'lead', - - where: filter.lead ? { - [Op.or]: [ - { id: { [Op.in]: filter.lead.split('|').map(term => Utils.uuid(term)) } }, - { - lead_name: { - [Op.or]: filter.lead.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.deals, - as: 'deal', - - where: filter.deal ? { - [Op.or]: [ - { id: { [Op.in]: filter.deal.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.deal.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.contacts, - as: 'contact', - - where: filter.contact ? { - [Op.or]: [ - { id: { [Op.in]: filter.contact.split('|').map(term => Utils.uuid(term)) } }, - { - full_name: { - [Op.or]: filter.contact.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.accounts, - as: 'account', - - where: filter.account ? { - [Op.or]: [ - { id: { [Op.in]: filter.account.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.account.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - - - { - model: db.file, - as: 'attachments', - }, - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.subject) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'activities', - 'subject', - filter.subject, - ), - }; - } - - if (filter.details) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'activities', - 'details', - filter.details, - ), - }; - } - - - - - if (filter.calendarStart && filter.calendarEnd) { - where = { - ...where, - [Op.or]: [ - { - due_at: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - { - completed_at: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - ], - }; - } - - - - if (filter.activity_atRange) { - const [start, end] = filter.activity_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - activity_at: { - ...where.activity_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - activity_at: { - ...where.activity_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.due_atRange) { - const [start, end] = filter.due_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - due_at: { - ...where.due_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - due_at: { - ...where.due_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.completed_atRange) { - const [start, end] = filter.completed_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - completed_at: { - ...where.completed_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - completed_at: { - ...where.completed_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.activity_type) { - where = { - ...where, - activity_type: filter.activity_type, - }; - } - - if (filter.status) { - where = { - ...where, - status: filter.status, - }; - } - - if (filter.priority) { - where = { - ...where, - priority: filter.priority, - }; - } - - - - - - - - - - - - - - - - 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.activities.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( - 'activities', - 'subject', - query, - ), - ], - }; - } - - const records = await db.activities.findAll({ - attributes: [ 'id', 'subject' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['subject', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.subject, - })); - } - - -}; - diff --git a/backend/src/db/api/contacts.js b/backend/src/db/api/contacts.js deleted file mode 100644 index dec6100..0000000 --- a/backend/src/db/api/contacts.js +++ /dev/null @@ -1,636 +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 ContactsDBApi { - - - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const contacts = await db.contacts.create( - { - id: data.id || undefined, - - full_name: data.full_name - || - null - , - - email: data.email - || - null - , - - phone: data.phone - || - null - , - - job_title: data.job_title - || - null - , - - contact_status: data.contact_status - || - null - , - - linkedin_url: data.linkedin_url - || - null - , - - notes: data.notes - || - null - , - - last_contacted_at: data.last_contacted_at - || - null - , - - next_follow_up_at: data.next_follow_up_at - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - - await contacts.setAccount( data.account || null, { - transaction, - }); - - await contacts.setOwner( data.owner || null, { - transaction, - }); - - - - - - - return contacts; - } - - - 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 contactsData = data.map((item, index) => ({ - id: item.id || undefined, - - full_name: item.full_name - || - null - , - - email: item.email - || - null - , - - phone: item.phone - || - null - , - - job_title: item.job_title - || - null - , - - contact_status: item.contact_status - || - null - , - - linkedin_url: item.linkedin_url - || - null - , - - notes: item.notes - || - null - , - - last_contacted_at: item.last_contacted_at - || - null - , - - next_follow_up_at: item.next_follow_up_at - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const contacts = await db.contacts.bulkCreate(contactsData, { transaction }); - - // For each item created, replace relation files - - - return contacts; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const contacts = await db.contacts.findByPk(id, {}, {transaction}); - - - - - const updatePayload = {}; - - if (data.full_name !== undefined) updatePayload.full_name = data.full_name; - - - if (data.email !== undefined) updatePayload.email = data.email; - - - if (data.phone !== undefined) updatePayload.phone = data.phone; - - - if (data.job_title !== undefined) updatePayload.job_title = data.job_title; - - - if (data.contact_status !== undefined) updatePayload.contact_status = data.contact_status; - - - if (data.linkedin_url !== undefined) updatePayload.linkedin_url = data.linkedin_url; - - - if (data.notes !== undefined) updatePayload.notes = data.notes; - - - if (data.last_contacted_at !== undefined) updatePayload.last_contacted_at = data.last_contacted_at; - - - if (data.next_follow_up_at !== undefined) updatePayload.next_follow_up_at = data.next_follow_up_at; - - - updatePayload.updatedById = currentUser.id; - - await contacts.update(updatePayload, {transaction}); - - - - if (data.account !== undefined) { - await contacts.setAccount( - - data.account, - - { transaction } - ); - } - - if (data.owner !== undefined) { - await contacts.setOwner( - - data.owner, - - { transaction } - ); - } - - - - - - - - return contacts; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const contacts = await db.contacts.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of contacts) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of contacts) { - await record.destroy({transaction}); - } - }); - - - return contacts; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const contacts = await db.contacts.findByPk(id, options); - - await contacts.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await contacts.destroy({ - transaction - }); - - return contacts; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const contacts = await db.contacts.findOne( - { where }, - { transaction }, - ); - - if (!contacts) { - return contacts; - } - - const output = contacts.get({plain: true}); - - - - - - - - - output.leads_primary_contact = await contacts.getLeads_primary_contact({ - transaction - }); - - - - output.deals_primary_contact = await contacts.getDeals_primary_contact({ - transaction - }); - - - output.activities_contact = await contacts.getActivities_contact({ - transaction - }); - - - - output.account = await contacts.getAccount({ - transaction - }); - - - output.owner = await contacts.getOwner({ - 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.accounts, - as: 'account', - - where: filter.account ? { - [Op.or]: [ - { id: { [Op.in]: filter.account.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.account.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.users, - as: 'owner', - - where: filter.owner ? { - [Op.or]: [ - { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } }, - { - firstName: { - [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.full_name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'contacts', - 'full_name', - filter.full_name, - ), - }; - } - - if (filter.email) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'contacts', - 'email', - filter.email, - ), - }; - } - - if (filter.phone) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'contacts', - 'phone', - filter.phone, - ), - }; - } - - if (filter.job_title) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'contacts', - 'job_title', - filter.job_title, - ), - }; - } - - if (filter.linkedin_url) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'contacts', - 'linkedin_url', - filter.linkedin_url, - ), - }; - } - - if (filter.notes) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'contacts', - 'notes', - filter.notes, - ), - }; - } - - - - - - - if (filter.last_contacted_atRange) { - const [start, end] = filter.last_contacted_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - last_contacted_at: { - ...where.last_contacted_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - last_contacted_at: { - ...where.last_contacted_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.next_follow_up_atRange) { - const [start, end] = filter.next_follow_up_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.contact_status) { - where = { - ...where, - contact_status: filter.contact_status, - }; - } - - - - - - - - - - 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.contacts.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( - 'contacts', - 'full_name', - query, - ), - ], - }; - } - - const records = await db.contacts.findAll({ - attributes: [ 'id', 'full_name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['full_name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.full_name, - })); - } - - -}; - diff --git a/backend/src/db/api/deals.js b/backend/src/db/api/deals.js deleted file mode 100644 index 0be9344..0000000 --- a/backend/src/db/api/deals.js +++ /dev/null @@ -1,802 +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 DealsDBApi { - - - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const deals = await db.deals.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - amount: data.amount - || - null - , - - currency: data.currency - || - null - , - - expected_close_at: data.expected_close_at - || - null - , - - closed_at: data.closed_at - || - null - , - - deal_status: data.deal_status - || - null - , - - loss_reason: data.loss_reason - || - null - , - - last_activity_at: data.last_activity_at - || - null - , - - next_follow_up_at: data.next_follow_up_at - || - null - , - - description: data.description - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - - await deals.setStage( data.stage || null, { - transaction, - }); - - await deals.setOwner( data.owner || null, { - transaction, - }); - - await deals.setAccount( data.account || null, { - transaction, - }); - - await deals.setPrimary_contact( data.primary_contact || null, { - transaction, - }); - - await deals.setOriginating_lead( data.originating_lead || null, { - transaction, - }); - - - - - - - return deals; - } - - - 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 dealsData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - amount: item.amount - || - null - , - - currency: item.currency - || - null - , - - expected_close_at: item.expected_close_at - || - null - , - - closed_at: item.closed_at - || - null - , - - deal_status: item.deal_status - || - null - , - - loss_reason: item.loss_reason - || - null - , - - last_activity_at: item.last_activity_at - || - null - , - - next_follow_up_at: item.next_follow_up_at - || - null - , - - description: item.description - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const deals = await db.deals.bulkCreate(dealsData, { transaction }); - - // For each item created, replace relation files - - - return deals; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const deals = await db.deals.findByPk(id, {}, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.amount !== undefined) updatePayload.amount = data.amount; - - - if (data.currency !== undefined) updatePayload.currency = data.currency; - - - if (data.expected_close_at !== undefined) updatePayload.expected_close_at = data.expected_close_at; - - - if (data.closed_at !== undefined) updatePayload.closed_at = data.closed_at; - - - if (data.deal_status !== undefined) updatePayload.deal_status = data.deal_status; - - - if (data.loss_reason !== undefined) updatePayload.loss_reason = data.loss_reason; - - - if (data.last_activity_at !== undefined) updatePayload.last_activity_at = data.last_activity_at; - - - if (data.next_follow_up_at !== undefined) updatePayload.next_follow_up_at = data.next_follow_up_at; - - - if (data.description !== undefined) updatePayload.description = data.description; - - - updatePayload.updatedById = currentUser.id; - - await deals.update(updatePayload, {transaction}); - - - - if (data.stage !== undefined) { - await deals.setStage( - - data.stage, - - { transaction } - ); - } - - if (data.owner !== undefined) { - await deals.setOwner( - - data.owner, - - { transaction } - ); - } - - if (data.account !== undefined) { - await deals.setAccount( - - data.account, - - { transaction } - ); - } - - if (data.primary_contact !== undefined) { - await deals.setPrimary_contact( - - data.primary_contact, - - { transaction } - ); - } - - if (data.originating_lead !== undefined) { - await deals.setOriginating_lead( - - data.originating_lead, - - { transaction } - ); - } - - - - - - - - return deals; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const deals = await db.deals.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of deals) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of deals) { - await record.destroy({transaction}); - } - }); - - - return deals; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const deals = await db.deals.findByPk(id, options); - - await deals.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await deals.destroy({ - transaction - }); - - return deals; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const deals = await db.deals.findOne( - { where }, - { transaction }, - ); - - if (!deals) { - return deals; - } - - const output = deals.get({plain: true}); - - - - - - - - - - - - output.activities_deal = await deals.getActivities_deal({ - transaction - }); - - - - output.stage = await deals.getStage({ - transaction - }); - - - output.owner = await deals.getOwner({ - transaction - }); - - - output.account = await deals.getAccount({ - transaction - }); - - - output.primary_contact = await deals.getPrimary_contact({ - transaction - }); - - - output.originating_lead = await deals.getOriginating_lead({ - 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.pipeline_stages, - as: 'stage', - - where: filter.stage ? { - [Op.or]: [ - { id: { [Op.in]: filter.stage.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.stage.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.users, - as: 'owner', - - where: filter.owner ? { - [Op.or]: [ - { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } }, - { - firstName: { - [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.accounts, - as: 'account', - - where: filter.account ? { - [Op.or]: [ - { id: { [Op.in]: filter.account.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.account.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.contacts, - as: 'primary_contact', - - where: filter.primary_contact ? { - [Op.or]: [ - { id: { [Op.in]: filter.primary_contact.split('|').map(term => Utils.uuid(term)) } }, - { - full_name: { - [Op.or]: filter.primary_contact.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.leads, - as: 'originating_lead', - - where: filter.originating_lead ? { - [Op.or]: [ - { id: { [Op.in]: filter.originating_lead.split('|').map(term => Utils.uuid(term)) } }, - { - lead_name: { - [Op.or]: filter.originating_lead.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'deals', - 'name', - filter.name, - ), - }; - } - - if (filter.currency) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'deals', - 'currency', - filter.currency, - ), - }; - } - - if (filter.loss_reason) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'deals', - 'loss_reason', - filter.loss_reason, - ), - }; - } - - if (filter.description) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'deals', - 'description', - filter.description, - ), - }; - } - - - - - - - if (filter.amountRange) { - const [start, end] = filter.amountRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - amount: { - ...where.amount, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - amount: { - ...where.amount, - [Op.lte]: end, - }, - }; - } - } - - if (filter.expected_close_atRange) { - const [start, end] = filter.expected_close_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - expected_close_at: { - ...where.expected_close_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - expected_close_at: { - ...where.expected_close_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.closed_atRange) { - const [start, end] = filter.closed_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - closed_at: { - ...where.closed_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - closed_at: { - ...where.closed_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.last_activity_atRange) { - const [start, end] = filter.last_activity_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - last_activity_at: { - ...where.last_activity_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - last_activity_at: { - ...where.last_activity_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.next_follow_up_atRange) { - const [start, end] = filter.next_follow_up_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.deal_status) { - where = { - ...where, - deal_status: filter.deal_status, - }; - } - - - - - - - - - - - - - - - - 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.deals.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( - 'deals', - 'name', - query, - ), - ], - }; - } - - const records = await db.deals.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } - - -}; - diff --git a/backend/src/db/api/lead_sources.js b/backend/src/db/api/lead_sources.js deleted file mode 100644 index 91f688e..0000000 --- a/backend/src/db/api/lead_sources.js +++ /dev/null @@ -1,386 +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 Lead_sourcesDBApi { - - - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const lead_sources = await db.lead_sources.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - is_active: data.is_active - || - false - - , - - description: data.description - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - - - - - - - return lead_sources; - } - - - 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 lead_sourcesData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - is_active: item.is_active - || - false - - , - - description: item.description - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const lead_sources = await db.lead_sources.bulkCreate(lead_sourcesData, { transaction }); - - // For each item created, replace relation files - - - return lead_sources; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const lead_sources = await db.lead_sources.findByPk(id, {}, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.is_active !== undefined) updatePayload.is_active = data.is_active; - - - if (data.description !== undefined) updatePayload.description = data.description; - - - updatePayload.updatedById = currentUser.id; - - await lead_sources.update(updatePayload, {transaction}); - - - - - - - - - - return lead_sources; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const lead_sources = await db.lead_sources.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of lead_sources) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of lead_sources) { - await record.destroy({transaction}); - } - }); - - - return lead_sources; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const lead_sources = await db.lead_sources.findByPk(id, options); - - await lead_sources.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await lead_sources.destroy({ - transaction - }); - - return lead_sources; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const lead_sources = await db.lead_sources.findOne( - { where }, - { transaction }, - ); - - if (!lead_sources) { - return lead_sources; - } - - const output = lead_sources.get({plain: true}); - - - - - - - - - output.leads_source = await lead_sources.getLeads_source({ - 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 = [ - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'lead_sources', - 'name', - filter.name, - ), - }; - } - - if (filter.description) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'lead_sources', - 'description', - filter.description, - ), - }; - } - - - - - - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.is_active) { - where = { - ...where, - is_active: filter.is_active, - }; - } - - - - - - 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.lead_sources.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( - 'lead_sources', - 'name', - query, - ), - ], - }; - } - - const records = await db.lead_sources.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } - - -}; - diff --git a/backend/src/db/api/leads.js b/backend/src/db/api/leads.js deleted file mode 100644 index 907201f..0000000 --- a/backend/src/db/api/leads.js +++ /dev/null @@ -1,702 +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 LeadsDBApi { - - - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const leads = await db.leads.create( - { - id: data.id || undefined, - - lead_name: data.lead_name - || - null - , - - company_name: data.company_name - || - null - , - - email: data.email - || - null - , - - phone: data.phone - || - null - , - - lead_status: data.lead_status - || - null - , - - lead_rating: data.lead_rating - || - null - , - - notes: data.notes - || - null - , - - last_contacted_at: data.last_contacted_at - || - null - , - - next_follow_up_at: data.next_follow_up_at - || - null - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - - await leads.setOwner( data.owner || null, { - transaction, - }); - - await leads.setSource( data.source || null, { - transaction, - }); - - await leads.setAccount( data.account || null, { - transaction, - }); - - await leads.setPrimary_contact( data.primary_contact || null, { - transaction, - }); - - - - - - - return leads; - } - - - 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 leadsData = data.map((item, index) => ({ - id: item.id || undefined, - - lead_name: item.lead_name - || - null - , - - company_name: item.company_name - || - null - , - - email: item.email - || - null - , - - phone: item.phone - || - null - , - - lead_status: item.lead_status - || - null - , - - lead_rating: item.lead_rating - || - null - , - - notes: item.notes - || - null - , - - last_contacted_at: item.last_contacted_at - || - null - , - - next_follow_up_at: item.next_follow_up_at - || - null - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const leads = await db.leads.bulkCreate(leadsData, { transaction }); - - // For each item created, replace relation files - - - return leads; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const leads = await db.leads.findByPk(id, {}, {transaction}); - - - - - const updatePayload = {}; - - if (data.lead_name !== undefined) updatePayload.lead_name = data.lead_name; - - - if (data.company_name !== undefined) updatePayload.company_name = data.company_name; - - - if (data.email !== undefined) updatePayload.email = data.email; - - - if (data.phone !== undefined) updatePayload.phone = data.phone; - - - if (data.lead_status !== undefined) updatePayload.lead_status = data.lead_status; - - - if (data.lead_rating !== undefined) updatePayload.lead_rating = data.lead_rating; - - - if (data.notes !== undefined) updatePayload.notes = data.notes; - - - if (data.last_contacted_at !== undefined) updatePayload.last_contacted_at = data.last_contacted_at; - - - if (data.next_follow_up_at !== undefined) updatePayload.next_follow_up_at = data.next_follow_up_at; - - - updatePayload.updatedById = currentUser.id; - - await leads.update(updatePayload, {transaction}); - - - - if (data.owner !== undefined) { - await leads.setOwner( - - data.owner, - - { transaction } - ); - } - - if (data.source !== undefined) { - await leads.setSource( - - data.source, - - { transaction } - ); - } - - if (data.account !== undefined) { - await leads.setAccount( - - data.account, - - { transaction } - ); - } - - if (data.primary_contact !== undefined) { - await leads.setPrimary_contact( - - data.primary_contact, - - { transaction } - ); - } - - - - - - - - return leads; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const leads = await db.leads.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of leads) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of leads) { - await record.destroy({transaction}); - } - }); - - - return leads; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const leads = await db.leads.findByPk(id, options); - - await leads.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await leads.destroy({ - transaction - }); - - return leads; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const leads = await db.leads.findOne( - { where }, - { transaction }, - ); - - if (!leads) { - return leads; - } - - const output = leads.get({plain: true}); - - - - - - - - - - - output.deals_originating_lead = await leads.getDeals_originating_lead({ - transaction - }); - - - output.activities_lead = await leads.getActivities_lead({ - transaction - }); - - - - output.owner = await leads.getOwner({ - transaction - }); - - - output.source = await leads.getSource({ - transaction - }); - - - output.account = await leads.getAccount({ - transaction - }); - - - output.primary_contact = await leads.getPrimary_contact({ - 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: 'owner', - - where: filter.owner ? { - [Op.or]: [ - { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } }, - { - firstName: { - [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.lead_sources, - as: 'source', - - where: filter.source ? { - [Op.or]: [ - { id: { [Op.in]: filter.source.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.source.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.accounts, - as: 'account', - - where: filter.account ? { - [Op.or]: [ - { id: { [Op.in]: filter.account.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.account.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.contacts, - as: 'primary_contact', - - where: filter.primary_contact ? { - [Op.or]: [ - { id: { [Op.in]: filter.primary_contact.split('|').map(term => Utils.uuid(term)) } }, - { - full_name: { - [Op.or]: filter.primary_contact.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.lead_name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'leads', - 'lead_name', - filter.lead_name, - ), - }; - } - - if (filter.company_name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'leads', - 'company_name', - filter.company_name, - ), - }; - } - - if (filter.email) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'leads', - 'email', - filter.email, - ), - }; - } - - if (filter.phone) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'leads', - 'phone', - filter.phone, - ), - }; - } - - if (filter.notes) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'leads', - 'notes', - filter.notes, - ), - }; - } - - - - - - - if (filter.last_contacted_atRange) { - const [start, end] = filter.last_contacted_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - last_contacted_at: { - ...where.last_contacted_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - last_contacted_at: { - ...where.last_contacted_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.next_follow_up_atRange) { - const [start, end] = filter.next_follow_up_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - next_follow_up_at: { - ...where.next_follow_up_at, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.lead_status) { - where = { - ...where, - lead_status: filter.lead_status, - }; - } - - if (filter.lead_rating) { - where = { - ...where, - lead_rating: filter.lead_rating, - }; - } - - - - - - - - - - - - - - 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.leads.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( - 'leads', - 'lead_name', - query, - ), - ], - }; - } - - const records = await db.leads.findAll({ - attributes: [ 'id', 'lead_name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['lead_name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.lead_name, - })); - } - - -}; - diff --git a/backend/src/db/api/pipeline_stages.js b/backend/src/db/api/pipeline_stages.js deleted file mode 100644 index 8774038..0000000 --- a/backend/src/db/api/pipeline_stages.js +++ /dev/null @@ -1,480 +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 Pipeline_stagesDBApi { - - - - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const pipeline_stages = await db.pipeline_stages.create( - { - id: data.id || undefined, - - name: data.name - || - null - , - - sort_order: data.sort_order - || - null - , - - win_probability: data.win_probability - || - null - , - - is_won: data.is_won - || - false - - , - - is_lost: data.is_lost - || - false - - , - - is_active: data.is_active - || - false - - , - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - - - - - - - return pipeline_stages; - } - - - 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 pipeline_stagesData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name - || - null - , - - sort_order: item.sort_order - || - null - , - - win_probability: item.win_probability - || - null - , - - is_won: item.is_won - || - false - - , - - is_lost: item.is_lost - || - false - - , - - is_active: item.is_active - || - false - - , - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const pipeline_stages = await db.pipeline_stages.bulkCreate(pipeline_stagesData, { transaction }); - - // For each item created, replace relation files - - - return pipeline_stages; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - - const pipeline_stages = await db.pipeline_stages.findByPk(id, {}, {transaction}); - - - - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.sort_order !== undefined) updatePayload.sort_order = data.sort_order; - - - if (data.win_probability !== undefined) updatePayload.win_probability = data.win_probability; - - - if (data.is_won !== undefined) updatePayload.is_won = data.is_won; - - - if (data.is_lost !== undefined) updatePayload.is_lost = data.is_lost; - - - if (data.is_active !== undefined) updatePayload.is_active = data.is_active; - - - updatePayload.updatedById = currentUser.id; - - await pipeline_stages.update(updatePayload, {transaction}); - - - - - - - - - - return pipeline_stages; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const pipeline_stages = await db.pipeline_stages.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of pipeline_stages) { - await record.update( - {deletedBy: currentUser.id}, - {transaction} - ); - } - for (const record of pipeline_stages) { - await record.destroy({transaction}); - } - }); - - - return pipeline_stages; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || {id: null}; - const transaction = (options && options.transaction) || undefined; - - const pipeline_stages = await db.pipeline_stages.findByPk(id, options); - - await pipeline_stages.update({ - deletedBy: currentUser.id - }, { - transaction, - }); - - await pipeline_stages.destroy({ - transaction - }); - - return pipeline_stages; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const pipeline_stages = await db.pipeline_stages.findOne( - { where }, - { transaction }, - ); - - if (!pipeline_stages) { - return pipeline_stages; - } - - const output = pipeline_stages.get({plain: true}); - - - - - - - - - - - output.deals_stage = await pipeline_stages.getDeals_stage({ - 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 = [ - - - - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'pipeline_stages', - 'name', - filter.name, - ), - }; - } - - - - - - - if (filter.sort_orderRange) { - const [start, end] = filter.sort_orderRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - sort_order: { - ...where.sort_order, - [Op.lte]: end, - }, - }; - } - } - - if (filter.win_probabilityRange) { - const [start, end] = filter.win_probabilityRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - win_probability: { - ...where.win_probability, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - win_probability: { - ...where.win_probability, - [Op.lte]: end, - }, - }; - } - } - - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true' - }; - } - - - if (filter.is_won) { - where = { - ...where, - is_won: filter.is_won, - }; - } - - if (filter.is_lost) { - where = { - ...where, - is_lost: filter.is_lost, - }; - } - - if (filter.is_active) { - where = { - ...where, - is_active: filter.is_active, - }; - } - - - - - - 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.pipeline_stages.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( - 'pipeline_stages', - 'name', - query, - ), - ], - }; - } - - const records = await db.pipeline_stages.findAll({ - attributes: [ 'id', 'name' ], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } - - -}; - diff --git a/backend/src/db/models/accounts.js b/backend/src/db/models/accounts.js deleted file mode 100644 index b40e875..0000000 --- a/backend/src/db/models/accounts.js +++ /dev/null @@ -1,207 +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 accounts = sequelize.define( - 'accounts', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - -name: { - type: DataTypes.TEXT, - - - - }, - -domain: { - type: DataTypes.TEXT, - - - - }, - -industry: { - type: DataTypes.TEXT, - - - - }, - -employee_count: { - type: DataTypes.INTEGER, - - - - }, - -annual_revenue: { - type: DataTypes.DECIMAL, - - - - }, - -phone: { - type: DataTypes.TEXT, - - - - }, - -website: { - type: DataTypes.TEXT, - - - - }, - -address: { - type: DataTypes.TEXT, - - - - }, - -account_status: { - type: DataTypes.ENUM, - - - - values: [ - -"prospect", - - -"active_customer", - - -"former_customer", - - -"partner" - - ], - - }, - -last_contacted_at: { - type: DataTypes.DATE, - - - - }, - -next_follow_up_at: { - type: DataTypes.DATE, - - - - }, - -notes: { - type: DataTypes.TEXT, - - - - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - accounts.associate = (db) => { - - -/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - - - - - - db.accounts.hasMany(db.contacts, { - as: 'contacts_account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - - - db.accounts.hasMany(db.leads, { - as: 'leads_account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - - - db.accounts.hasMany(db.deals, { - as: 'deals_account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - - db.accounts.hasMany(db.activities, { - as: 'activities_account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - - -//end loop - - - - db.accounts.belongsTo(db.users, { - as: 'owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - - - - db.accounts.belongsTo(db.users, { - as: 'createdBy', - }); - - db.accounts.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - - - return accounts; -}; - - diff --git a/backend/src/db/models/activities.js b/backend/src/db/models/activities.js deleted file mode 100644 index 870b89e..0000000 --- a/backend/src/db/models/activities.js +++ /dev/null @@ -1,216 +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 activities = sequelize.define( - 'activities', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - -activity_type: { - type: DataTypes.ENUM, - - - - values: [ - -"call", - - -"email", - - -"meeting", - - -"task", - - -"note" - - ], - - }, - -subject: { - type: DataTypes.TEXT, - - - - }, - -details: { - type: DataTypes.TEXT, - - - - }, - -activity_at: { - type: DataTypes.DATE, - - - - }, - -due_at: { - type: DataTypes.DATE, - - - - }, - -completed_at: { - type: DataTypes.DATE, - - - - }, - -status: { - type: DataTypes.ENUM, - - - - values: [ - -"planned", - - -"completed", - - -"canceled" - - ], - - }, - -priority: { - type: DataTypes.ENUM, - - - - values: [ - -"low", - - -"medium", - - -"high" - - ], - - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - activities.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.activities.belongsTo(db.users, { - as: 'owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - db.activities.belongsTo(db.leads, { - as: 'lead', - foreignKey: { - name: 'leadId', - }, - constraints: false, - }); - - db.activities.belongsTo(db.deals, { - as: 'deal', - foreignKey: { - name: 'dealId', - }, - constraints: false, - }); - - db.activities.belongsTo(db.contacts, { - as: 'contact', - foreignKey: { - name: 'contactId', - }, - constraints: false, - }); - - db.activities.belongsTo(db.accounts, { - as: 'account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - - - db.activities.hasMany(db.file, { - as: 'attachments', - foreignKey: 'belongsToId', - constraints: false, - scope: { - belongsTo: db.activities.getTableName(), - belongsToColumn: 'attachments', - }, - }); - - - db.activities.belongsTo(db.users, { - as: 'createdBy', - }); - - db.activities.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - - - return activities; -}; - - diff --git a/backend/src/db/models/contacts.js b/backend/src/db/models/contacts.js deleted file mode 100644 index 4647498..0000000 --- a/backend/src/db/models/contacts.js +++ /dev/null @@ -1,186 +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 contacts = sequelize.define( - 'contacts', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - -full_name: { - type: DataTypes.TEXT, - - - - }, - -email: { - type: DataTypes.TEXT, - - - - }, - -phone: { - type: DataTypes.TEXT, - - - - }, - -job_title: { - type: DataTypes.TEXT, - - - - }, - -contact_status: { - type: DataTypes.ENUM, - - - - values: [ - -"active", - - -"do_not_contact", - - -"bounced", - - -"unsubscribed" - - ], - - }, - -linkedin_url: { - type: DataTypes.TEXT, - - - - }, - -notes: { - type: DataTypes.TEXT, - - - - }, - -last_contacted_at: { - type: DataTypes.DATE, - - - - }, - -next_follow_up_at: { - type: DataTypes.DATE, - - - - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - contacts.associate = (db) => { - - -/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - - - - - - - - db.contacts.hasMany(db.leads, { - as: 'leads_primary_contact', - foreignKey: { - name: 'primary_contactId', - }, - constraints: false, - }); - - - - db.contacts.hasMany(db.deals, { - as: 'deals_primary_contact', - foreignKey: { - name: 'primary_contactId', - }, - constraints: false, - }); - - - db.contacts.hasMany(db.activities, { - as: 'activities_contact', - foreignKey: { - name: 'contactId', - }, - constraints: false, - }); - - - -//end loop - - - - db.contacts.belongsTo(db.accounts, { - as: 'account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - db.contacts.belongsTo(db.users, { - as: 'owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - - - - db.contacts.belongsTo(db.users, { - as: 'createdBy', - }); - - db.contacts.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - - - return contacts; -}; - - diff --git a/backend/src/db/models/deals.js b/backend/src/db/models/deals.js deleted file mode 100644 index 0a86fd8..0000000 --- a/backend/src/db/models/deals.js +++ /dev/null @@ -1,198 +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 deals = sequelize.define( - 'deals', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - -name: { - type: DataTypes.TEXT, - - - - }, - -amount: { - type: DataTypes.DECIMAL, - - - - }, - -currency: { - type: DataTypes.TEXT, - - - - }, - -expected_close_at: { - type: DataTypes.DATE, - - - - }, - -closed_at: { - type: DataTypes.DATE, - - - - }, - -deal_status: { - type: DataTypes.ENUM, - - - - values: [ - -"open", - - -"won", - - -"lost" - - ], - - }, - -loss_reason: { - type: DataTypes.TEXT, - - - - }, - -last_activity_at: { - type: DataTypes.DATE, - - - - }, - -next_follow_up_at: { - type: DataTypes.DATE, - - - - }, - -description: { - type: DataTypes.TEXT, - - - - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - deals.associate = (db) => { - - -/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - - - - - - - - - - - db.deals.hasMany(db.activities, { - as: 'activities_deal', - foreignKey: { - name: 'dealId', - }, - constraints: false, - }); - - - -//end loop - - - - db.deals.belongsTo(db.pipeline_stages, { - as: 'stage', - foreignKey: { - name: 'stageId', - }, - constraints: false, - }); - - db.deals.belongsTo(db.users, { - as: 'owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - db.deals.belongsTo(db.accounts, { - as: 'account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - db.deals.belongsTo(db.contacts, { - as: 'primary_contact', - foreignKey: { - name: 'primary_contactId', - }, - constraints: false, - }); - - db.deals.belongsTo(db.leads, { - as: 'originating_lead', - foreignKey: { - name: 'originating_leadId', - }, - constraints: false, - }); - - - - - db.deals.belongsTo(db.users, { - as: 'createdBy', - }); - - db.deals.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - - - return deals; -}; - - diff --git a/backend/src/db/models/lead_sources.js b/backend/src/db/models/lead_sources.js deleted file mode 100644 index 243a34e..0000000 --- a/backend/src/db/models/lead_sources.js +++ /dev/null @@ -1,100 +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 lead_sources = sequelize.define( - 'lead_sources', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - -name: { - type: DataTypes.TEXT, - - - - }, - -is_active: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - - - - }, - -description: { - type: DataTypes.TEXT, - - - - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - lead_sources.associate = (db) => { - - -/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - - - - - - - - db.lead_sources.hasMany(db.leads, { - as: 'leads_source', - foreignKey: { - name: 'sourceId', - }, - constraints: false, - }); - - - - - - -//end loop - - - - - - - db.lead_sources.belongsTo(db.users, { - as: 'createdBy', - }); - - db.lead_sources.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - - - return lead_sources; -}; - - diff --git a/backend/src/db/models/leads.js b/backend/src/db/models/leads.js deleted file mode 100644 index 069b54f..0000000 --- a/backend/src/db/models/leads.js +++ /dev/null @@ -1,209 +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 leads = sequelize.define( - 'leads', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - -lead_name: { - type: DataTypes.TEXT, - - - - }, - -company_name: { - type: DataTypes.TEXT, - - - - }, - -email: { - type: DataTypes.TEXT, - - - - }, - -phone: { - type: DataTypes.TEXT, - - - - }, - -lead_status: { - type: DataTypes.ENUM, - - - - values: [ - -"new", - - -"contacted", - - -"qualified", - - -"unqualified", - - -"converted" - - ], - - }, - -lead_rating: { - type: DataTypes.ENUM, - - - - values: [ - -"cold", - - -"warm", - - -"hot" - - ], - - }, - -notes: { - type: DataTypes.TEXT, - - - - }, - -last_contacted_at: { - type: DataTypes.DATE, - - - - }, - -next_follow_up_at: { - type: DataTypes.DATE, - - - - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - leads.associate = (db) => { - - -/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - - - - - - - - - - db.leads.hasMany(db.deals, { - as: 'deals_originating_lead', - foreignKey: { - name: 'originating_leadId', - }, - constraints: false, - }); - - - db.leads.hasMany(db.activities, { - as: 'activities_lead', - foreignKey: { - name: 'leadId', - }, - constraints: false, - }); - - - -//end loop - - - - db.leads.belongsTo(db.users, { - as: 'owner', - foreignKey: { - name: 'ownerId', - }, - constraints: false, - }); - - db.leads.belongsTo(db.lead_sources, { - as: 'source', - foreignKey: { - name: 'sourceId', - }, - constraints: false, - }); - - db.leads.belongsTo(db.accounts, { - as: 'account', - foreignKey: { - name: 'accountId', - }, - constraints: false, - }); - - db.leads.belongsTo(db.contacts, { - as: 'primary_contact', - foreignKey: { - name: 'primary_contactId', - }, - constraints: false, - }); - - - - - db.leads.belongsTo(db.users, { - as: 'createdBy', - }); - - db.leads.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - - - return leads; -}; - - diff --git a/backend/src/db/models/pipeline_stages.js b/backend/src/db/models/pipeline_stages.js deleted file mode 100644 index a4da82f..0000000 --- a/backend/src/db/models/pipeline_stages.js +++ /dev/null @@ -1,127 +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 pipeline_stages = sequelize.define( - 'pipeline_stages', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - -name: { - type: DataTypes.TEXT, - - - - }, - -sort_order: { - type: DataTypes.INTEGER, - - - - }, - -win_probability: { - type: DataTypes.DECIMAL, - - - - }, - -is_won: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - - - - }, - -is_lost: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - - - - }, - -is_active: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - - - - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - pipeline_stages.associate = (db) => { - - -/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - - - - - - - - - - db.pipeline_stages.hasMany(db.deals, { - as: 'deals_stage', - foreignKey: { - name: 'stageId', - }, - constraints: false, - }); - - - - -//end loop - - - - - - - db.pipeline_stages.belongsTo(db.users, { - as: 'createdBy', - }); - - db.pipeline_stages.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - - - return pipeline_stages; -}; - - diff --git a/backend/src/routes/accounts.js b/backend/src/routes/accounts.js deleted file mode 100644 index 3acf160..0000000 --- a/backend/src/routes/accounts.js +++ /dev/null @@ -1,454 +0,0 @@ - -const express = require('express'); - -const AccountsService = require('../services/accounts'); -const AccountsDBApi = require('../db/api/accounts'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('accounts')); - - -/** - * @swagger - * components: - * schemas: - * Accounts: - * type: object - * properties: - - * name: - * type: string - * default: name - * domain: - * type: string - * default: domain - * industry: - * type: string - * default: industry - * phone: - * type: string - * default: phone - * website: - * type: string - * default: website - * address: - * type: string - * default: address - * notes: - * type: string - * default: notes - - * employee_count: - * type: integer - * format: int64 - - * annual_revenue: - * type: integer - * format: int64 - - * - */ - -/** - * @swagger - * tags: - * name: Accounts - * description: The Accounts managing API - */ - -/** -* @swagger -* /api/accounts: -* post: -* security: -* - bearerAuth: [] -* tags: [Accounts] -* 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/Accounts" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Accounts" -* 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 AccountsService.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: [Accounts] - * 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/Accounts" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Accounts" - * 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 AccountsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/accounts/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Accounts] - * 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/Accounts" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Accounts" - * 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 AccountsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/accounts/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Accounts] - * 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/Accounts" - * 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 AccountsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/accounts/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Accounts] - * 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/Accounts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await AccountsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/accounts: - * get: - * security: - * - bearerAuth: [] - * tags: [Accounts] - * summary: Get all accounts - * description: Get all accounts - * responses: - * 200: - * description: Accounts list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Accounts" - * 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 AccountsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name','domain','industry','phone','website','address','notes', - 'employee_count', - 'annual_revenue', - 'last_contacted_at','next_follow_up_at', - ]; - 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/accounts/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Accounts] - * summary: Count all accounts - * description: Count all accounts - * responses: - * 200: - * description: Accounts count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Accounts" - * 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 AccountsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/accounts/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Accounts] - * summary: Find all accounts that match search criteria - * description: Find all accounts that match search criteria - * responses: - * 200: - * description: Accounts list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Accounts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await AccountsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/accounts/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Accounts] - * 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/Accounts" - * 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 AccountsDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/activities.js b/backend/src/routes/activities.js deleted file mode 100644 index 603f1a9..0000000 --- a/backend/src/routes/activities.js +++ /dev/null @@ -1,435 +0,0 @@ - -const express = require('express'); - -const ActivitiesService = require('../services/activities'); -const ActivitiesDBApi = require('../db/api/activities'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('activities')); - - -/** - * @swagger - * components: - * schemas: - * Activities: - * type: object - * properties: - - * subject: - * type: string - * default: subject - * details: - * type: string - * default: details - - - - * - * - * - */ - -/** - * @swagger - * tags: - * name: Activities - * description: The Activities managing API - */ - -/** -* @swagger -* /api/activities: -* post: -* security: -* - bearerAuth: [] -* tags: [Activities] -* 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/Activities" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Activities" -* 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 ActivitiesService.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: [Activities] - * 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/Activities" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Activities" - * 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 ActivitiesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/activities/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Activities] - * 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/Activities" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Activities" - * 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 ActivitiesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/activities/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Activities] - * 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/Activities" - * 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 ActivitiesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/activities/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Activities] - * 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/Activities" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await ActivitiesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/activities: - * get: - * security: - * - bearerAuth: [] - * tags: [Activities] - * summary: Get all activities - * description: Get all activities - * responses: - * 200: - * description: Activities list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Activities" - * 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 ActivitiesDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','subject','details', - - - 'activity_at','due_at','completed_at', - ]; - 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/activities/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Activities] - * summary: Count all activities - * description: Count all activities - * responses: - * 200: - * description: Activities count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Activities" - * 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 ActivitiesDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/activities/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Activities] - * summary: Find all activities that match search criteria - * description: Find all activities that match search criteria - * responses: - * 200: - * description: Activities list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Activities" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await ActivitiesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/activities/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Activities] - * 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/Activities" - * 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 ActivitiesDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/contacts.js b/backend/src/routes/contacts.js deleted file mode 100644 index 3285671..0000000 --- a/backend/src/routes/contacts.js +++ /dev/null @@ -1,445 +0,0 @@ - -const express = require('express'); - -const ContactsService = require('../services/contacts'); -const ContactsDBApi = require('../db/api/contacts'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('contacts')); - - -/** - * @swagger - * components: - * schemas: - * Contacts: - * type: object - * properties: - - * full_name: - * type: string - * default: full_name - * email: - * type: string - * default: email - * phone: - * type: string - * default: phone - * job_title: - * type: string - * default: job_title - * linkedin_url: - * type: string - * default: linkedin_url - * notes: - * type: string - * default: notes - - - - * - */ - -/** - * @swagger - * tags: - * name: Contacts - * description: The Contacts managing API - */ - -/** -* @swagger -* /api/contacts: -* post: -* security: -* - bearerAuth: [] -* tags: [Contacts] -* 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/Contacts" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Contacts" -* 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 ContactsService.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: [Contacts] - * 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/Contacts" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Contacts" - * 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 ContactsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/contacts/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Contacts] - * 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/Contacts" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Contacts" - * 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 ContactsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/contacts/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Contacts] - * 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/Contacts" - * 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 ContactsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/contacts/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Contacts] - * 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/Contacts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await ContactsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/contacts: - * get: - * security: - * - bearerAuth: [] - * tags: [Contacts] - * summary: Get all contacts - * description: Get all contacts - * responses: - * 200: - * description: Contacts list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Contacts" - * 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 ContactsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','full_name','email','phone','job_title','linkedin_url','notes', - - - 'last_contacted_at','next_follow_up_at', - ]; - 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/contacts/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Contacts] - * summary: Count all contacts - * description: Count all contacts - * responses: - * 200: - * description: Contacts count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Contacts" - * 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 ContactsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/contacts/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Contacts] - * summary: Find all contacts that match search criteria - * description: Find all contacts that match search criteria - * responses: - * 200: - * description: Contacts list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Contacts" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await ContactsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/contacts/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Contacts] - * 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/Contacts" - * 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 ContactsDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/deals.js b/backend/src/routes/deals.js deleted file mode 100644 index a35e517..0000000 --- a/backend/src/routes/deals.js +++ /dev/null @@ -1,442 +0,0 @@ - -const express = require('express'); - -const DealsService = require('../services/deals'); -const DealsDBApi = require('../db/api/deals'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('deals')); - - -/** - * @swagger - * components: - * schemas: - * Deals: - * type: object - * properties: - - * name: - * type: string - * default: name - * currency: - * type: string - * default: currency - * loss_reason: - * type: string - * default: loss_reason - * description: - * type: string - * default: description - - - * amount: - * type: integer - * format: int64 - - * - */ - -/** - * @swagger - * tags: - * name: Deals - * description: The Deals managing API - */ - -/** -* @swagger -* /api/deals: -* post: -* security: -* - bearerAuth: [] -* tags: [Deals] -* 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/Deals" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Deals" -* 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 DealsService.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: [Deals] - * 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/Deals" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Deals" - * 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 DealsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/deals/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Deals] - * 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/Deals" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Deals" - * 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 DealsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/deals/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Deals] - * 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/Deals" - * 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 DealsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/deals/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Deals] - * 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/Deals" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await DealsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/deals: - * get: - * security: - * - bearerAuth: [] - * tags: [Deals] - * summary: Get all deals - * description: Get all deals - * responses: - * 200: - * description: Deals list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Deals" - * 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 DealsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name','currency','loss_reason','description', - - 'amount', - 'expected_close_at','closed_at','last_activity_at','next_follow_up_at', - ]; - 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/deals/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Deals] - * summary: Count all deals - * description: Count all deals - * responses: - * 200: - * description: Deals count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Deals" - * 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 DealsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/deals/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Deals] - * summary: Find all deals that match search criteria - * description: Find all deals that match search criteria - * responses: - * 200: - * description: Deals list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Deals" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await DealsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/deals/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Deals] - * 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/Deals" - * 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 DealsDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/lead_sources.js b/backend/src/routes/lead_sources.js deleted file mode 100644 index 8143653..0000000 --- a/backend/src/routes/lead_sources.js +++ /dev/null @@ -1,432 +0,0 @@ - -const express = require('express'); - -const Lead_sourcesService = require('../services/lead_sources'); -const Lead_sourcesDBApi = require('../db/api/lead_sources'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('lead_sources')); - - -/** - * @swagger - * components: - * schemas: - * Lead_sources: - * type: object - * properties: - - * name: - * type: string - * default: name - * description: - * type: string - * default: description - - - - */ - -/** - * @swagger - * tags: - * name: Lead_sources - * description: The Lead_sources managing API - */ - -/** -* @swagger -* /api/lead_sources: -* post: -* security: -* - bearerAuth: [] -* tags: [Lead_sources] -* 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/Lead_sources" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Lead_sources" -* 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 Lead_sourcesService.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: [Lead_sources] - * 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/Lead_sources" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Lead_sources" - * 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 Lead_sourcesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/lead_sources/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Lead_sources] - * 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/Lead_sources" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Lead_sources" - * 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 Lead_sourcesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/lead_sources/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Lead_sources] - * 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/Lead_sources" - * 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 Lead_sourcesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/lead_sources/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Lead_sources] - * 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/Lead_sources" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Lead_sourcesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/lead_sources: - * get: - * security: - * - bearerAuth: [] - * tags: [Lead_sources] - * summary: Get all lead_sources - * description: Get all lead_sources - * responses: - * 200: - * description: Lead_sources list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Lead_sources" - * 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 Lead_sourcesDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name','description', - - - - ]; - 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/lead_sources/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Lead_sources] - * summary: Count all lead_sources - * description: Count all lead_sources - * responses: - * 200: - * description: Lead_sources count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Lead_sources" - * 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 Lead_sourcesDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/lead_sources/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Lead_sources] - * summary: Find all lead_sources that match search criteria - * description: Find all lead_sources that match search criteria - * responses: - * 200: - * description: Lead_sources list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Lead_sources" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Lead_sourcesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/lead_sources/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Lead_sources] - * 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/Lead_sources" - * 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 Lead_sourcesDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/leads.js b/backend/src/routes/leads.js deleted file mode 100644 index 9bc62ce..0000000 --- a/backend/src/routes/leads.js +++ /dev/null @@ -1,443 +0,0 @@ - -const express = require('express'); - -const LeadsService = require('../services/leads'); -const LeadsDBApi = require('../db/api/leads'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('leads')); - - -/** - * @swagger - * components: - * schemas: - * Leads: - * type: object - * properties: - - * lead_name: - * type: string - * default: lead_name - * company_name: - * type: string - * default: company_name - * email: - * type: string - * default: email - * phone: - * type: string - * default: phone - * notes: - * type: string - * default: notes - - - - * - * - */ - -/** - * @swagger - * tags: - * name: Leads - * description: The Leads managing API - */ - -/** -* @swagger -* /api/leads: -* post: -* security: -* - bearerAuth: [] -* tags: [Leads] -* 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/Leads" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Leads" -* 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 LeadsService.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: [Leads] - * 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/Leads" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Leads" - * 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 LeadsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/leads/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Leads] - * 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/Leads" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Leads" - * 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 LeadsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/leads/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Leads] - * 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/Leads" - * 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 LeadsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/leads/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Leads] - * 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/Leads" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await LeadsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/leads: - * get: - * security: - * - bearerAuth: [] - * tags: [Leads] - * summary: Get all leads - * description: Get all leads - * responses: - * 200: - * description: Leads list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Leads" - * 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 LeadsDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','lead_name','company_name','email','phone','notes', - - - 'last_contacted_at','next_follow_up_at', - ]; - 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/leads/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Leads] - * summary: Count all leads - * description: Count all leads - * responses: - * 200: - * description: Leads count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Leads" - * 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 LeadsDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/leads/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Leads] - * summary: Find all leads that match search criteria - * description: Find all leads that match search criteria - * responses: - * 200: - * description: Leads list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Leads" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await LeadsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/leads/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Leads] - * 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/Leads" - * 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 LeadsDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/pipeline_stages.js b/backend/src/routes/pipeline_stages.js deleted file mode 100644 index 0eff342..0000000 --- a/backend/src/routes/pipeline_stages.js +++ /dev/null @@ -1,435 +0,0 @@ - -const express = require('express'); - -const Pipeline_stagesService = require('../services/pipeline_stages'); -const Pipeline_stagesDBApi = require('../db/api/pipeline_stages'); -const wrapAsync = require('../helpers').wrapAsync; - - -const router = express.Router(); - -const { parse } = require('json2csv'); - - -const { - checkCrudPermissions, -} = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('pipeline_stages')); - - -/** - * @swagger - * components: - * schemas: - * Pipeline_stages: - * type: object - * properties: - - * name: - * type: string - * default: name - - * sort_order: - * type: integer - * format: int64 - - * win_probability: - * type: integer - * format: int64 - - */ - -/** - * @swagger - * tags: - * name: Pipeline_stages - * description: The Pipeline_stages managing API - */ - -/** -* @swagger -* /api/pipeline_stages: -* post: -* security: -* - bearerAuth: [] -* tags: [Pipeline_stages] -* 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/Pipeline_stages" -* responses: -* 200: -* description: The item was successfully added -* content: -* application/json: -* schema: -* $ref: "#/components/schemas/Pipeline_stages" -* 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 Pipeline_stagesService.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: [Pipeline_stages] - * 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/Pipeline_stages" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Pipeline_stages" - * 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 Pipeline_stagesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pipeline_stages/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Pipeline_stages] - * 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/Pipeline_stages" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Pipeline_stages" - * 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 Pipeline_stagesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pipeline_stages/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Pipeline_stages] - * 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/Pipeline_stages" - * 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 Pipeline_stagesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pipeline_stages/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Pipeline_stages] - * 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/Pipeline_stages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Pipeline_stagesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - })); - -/** - * @swagger - * /api/pipeline_stages: - * get: - * security: - * - bearerAuth: [] - * tags: [Pipeline_stages] - * summary: Get all pipeline_stages - * description: Get all pipeline_stages - * responses: - * 200: - * description: Pipeline_stages list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Pipeline_stages" - * 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 Pipeline_stagesDBApi.findAll( - req.query, { currentUser } - ); - if (filetype && filetype === 'csv') { - const fields = ['id','name', - 'sort_order', - 'win_probability', - - ]; - 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/pipeline_stages/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Pipeline_stages] - * summary: Count all pipeline_stages - * description: Count all pipeline_stages - * responses: - * 200: - * description: Pipeline_stages count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Pipeline_stages" - * 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 Pipeline_stagesDBApi.findAll( - req.query, - null, - { countOnly: true, currentUser } - ); - - res.status(200).send(payload); -})); - -/** - * @swagger - * /api/pipeline_stages/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Pipeline_stages] - * summary: Find all pipeline_stages that match search criteria - * description: Find all pipeline_stages that match search criteria - * responses: - * 200: - * description: Pipeline_stages list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Pipeline_stages" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - - const payload = await Pipeline_stagesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/pipeline_stages/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Pipeline_stages] - * 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/Pipeline_stages" - * 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 Pipeline_stagesDBApi.findBy( - { id: req.params.id }, - ); - - - - res.status(200).send(payload); -})); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/services/accounts.js b/backend/src/services/accounts.js deleted file mode 100644 index ca60a54..0000000 --- a/backend/src/services/accounts.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const AccountsDBApi = require('../db/api/accounts'); -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 AccountsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await AccountsDBApi.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 AccountsDBApi.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 accounts = await AccountsDBApi.findBy( - {id}, - {transaction}, - ); - - if (!accounts) { - throw new ValidationError( - 'accountsNotFound', - ); - } - - const updatedAccounts = await AccountsDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedAccounts; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await AccountsDBApi.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 AccountsDBApi.remove( - id, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - -}; - - diff --git a/backend/src/services/activities.js b/backend/src/services/activities.js deleted file mode 100644 index 8fc3266..0000000 --- a/backend/src/services/activities.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const ActivitiesDBApi = require('../db/api/activities'); -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 ActivitiesService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await ActivitiesDBApi.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 ActivitiesDBApi.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 activities = await ActivitiesDBApi.findBy( - {id}, - {transaction}, - ); - - if (!activities) { - throw new ValidationError( - 'activitiesNotFound', - ); - } - - const updatedActivities = await ActivitiesDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedActivities; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await ActivitiesDBApi.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 ActivitiesDBApi.remove( - id, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - -}; - - diff --git a/backend/src/services/contacts.js b/backend/src/services/contacts.js deleted file mode 100644 index 7189d73..0000000 --- a/backend/src/services/contacts.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const ContactsDBApi = require('../db/api/contacts'); -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 ContactsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await ContactsDBApi.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 ContactsDBApi.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 contacts = await ContactsDBApi.findBy( - {id}, - {transaction}, - ); - - if (!contacts) { - throw new ValidationError( - 'contactsNotFound', - ); - } - - const updatedContacts = await ContactsDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedContacts; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await ContactsDBApi.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 ContactsDBApi.remove( - id, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - -}; - - diff --git a/backend/src/services/deals.js b/backend/src/services/deals.js deleted file mode 100644 index a611dab..0000000 --- a/backend/src/services/deals.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const DealsDBApi = require('../db/api/deals'); -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 DealsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await DealsDBApi.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 DealsDBApi.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 deals = await DealsDBApi.findBy( - {id}, - {transaction}, - ); - - if (!deals) { - throw new ValidationError( - 'dealsNotFound', - ); - } - - const updatedDeals = await DealsDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedDeals; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await DealsDBApi.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 DealsDBApi.remove( - id, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - -}; - - diff --git a/backend/src/services/lead_sources.js b/backend/src/services/lead_sources.js deleted file mode 100644 index a216daf..0000000 --- a/backend/src/services/lead_sources.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const Lead_sourcesDBApi = require('../db/api/lead_sources'); -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 Lead_sourcesService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await Lead_sourcesDBApi.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 Lead_sourcesDBApi.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 lead_sources = await Lead_sourcesDBApi.findBy( - {id}, - {transaction}, - ); - - if (!lead_sources) { - throw new ValidationError( - 'lead_sourcesNotFound', - ); - } - - const updatedLead_sources = await Lead_sourcesDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedLead_sources; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await Lead_sourcesDBApi.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 Lead_sourcesDBApi.remove( - id, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - -}; - - diff --git a/backend/src/services/leads.js b/backend/src/services/leads.js deleted file mode 100644 index 4aff6d5..0000000 --- a/backend/src/services/leads.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const LeadsDBApi = require('../db/api/leads'); -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 LeadsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await LeadsDBApi.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 LeadsDBApi.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 leads = await LeadsDBApi.findBy( - {id}, - {transaction}, - ); - - if (!leads) { - throw new ValidationError( - 'leadsNotFound', - ); - } - - const updatedLeads = await LeadsDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedLeads; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await LeadsDBApi.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 LeadsDBApi.remove( - id, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - -}; - - diff --git a/backend/src/services/notifications/list.js b/backend/src/services/notifications/list.js index 17242b0..5c50707 100644 --- a/backend/src/services/notifications/list.js +++ b/backend/src/services/notifications/list.js @@ -1,6 +1,6 @@ const errors = { app: { - title: 'Sales Pipeline CRM', + title: 'Coaching SaaS Workspace', }, auth: { diff --git a/backend/src/services/pipeline_stages.js b/backend/src/services/pipeline_stages.js deleted file mode 100644 index 762b458..0000000 --- a/backend/src/services/pipeline_stages.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const Pipeline_stagesDBApi = require('../db/api/pipeline_stages'); -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 Pipeline_stagesService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await Pipeline_stagesDBApi.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 Pipeline_stagesDBApi.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 pipeline_stages = await Pipeline_stagesDBApi.findBy( - {id}, - {transaction}, - ); - - if (!pipeline_stages) { - throw new ValidationError( - 'pipeline_stagesNotFound', - ); - } - - const updatedPipeline_stages = await Pipeline_stagesDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedPipeline_stages; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await Pipeline_stagesDBApi.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 Pipeline_stagesDBApi.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 190a1fc..dafa5aa 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -1,15 +1,9 @@ const db = require('../db/models'); const ValidationError = require('./notifications/errors/validation'); -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; +const Op = db.Sequelize.Op; -/** - * @param {string} permission - * @param {object} currentUser - */ async function checkPermissions(permission, currentUser) { - if (!currentUser) { throw new ValidationError('auth.unauthorized'); } @@ -22,288 +16,93 @@ async function checkPermissions(permission, currentUser) { return true; } - try { - if (!currentUser.app_role) { - throw new ValidationError('auth.forbidden'); - } - - const permissions = await currentUser.app_role.getPermissions(); - - return !!permissions.find((p) => p.name === permission); - } catch (e) { - throw e; + if (!currentUser.app_role) { + throw new ValidationError('auth.forbidden'); } + + const permissions = await currentUser.app_role.getPermissions(); + return !!permissions.find((p) => p.name === permission); +} + +function textMatch(fields, searchQuery) { + return { + [Op.or]: fields.map((field) => ({ + [field]: { [Op.iLike]: `%${searchQuery}%` }, + })), + }; +} + +function searchResult(type, label, description, path) { + return { + type, + label, + description, + path, + }; } module.exports = class SearchService { - static async search(searchQuery, currentUser ) { - try { - if (!searchQuery) { - throw new ValidationError('iam.errors.searchQueryRequired'); - } - const tableColumns = { - - - - - - "users": [ - - "firstName", - - "lastName", - - "phoneNumber", - - "email", - - ], - - - - - - - - - "accounts": [ - - "name", - - "domain", - - "industry", - - "phone", - - "website", - - "address", - - "notes", - - ], - - - - - - - "contacts": [ - - "full_name", - - "email", - - "phone", - - "job_title", - - "linkedin_url", - - "notes", - - ], - - - - - - - "lead_sources": [ - - "name", - - "description", - - ], - - - - - - - "leads": [ - - "lead_name", - - "company_name", - - "email", - - "phone", - - "notes", - - ], - - - - - - - "pipeline_stages": [ - - "name", - - ], - - - - - - - "deals": [ - - "name", - - "currency", - - "loss_reason", - - "description", - - ], - - - - - - - "activities": [ - - "subject", - - "details", - - ], - - - }; - const columnsInt = { - - - - - - - - - - - "accounts": [ - - "employee_count", - - "annual_revenue", - - ], - - - - - - - - - - - - - - - - - - "pipeline_stages": [ - - "sort_order", - - "win_probability", - - ], - - - - - - "deals": [ - - "amount", - - ], - - - - - - - }; - - let allFoundRecords = []; - - for (const tableName in tableColumns) { - if (tableColumns.hasOwnProperty(tableName)) { - const attributesToSearch = tableColumns[tableName]; - const attributesIntToSearch = columnsInt[tableName] || []; - const whereCondition = { - [Op.or]: [ - ...attributesToSearch.map(attribute => ({ - [attribute]: { - [Op.iLike] : `%${searchQuery}%`, - }, - })), - ...attributesIntToSearch.map(attribute => ( - Sequelize.where( - Sequelize.cast(Sequelize.col(`${tableName}.${attribute}`), 'varchar'), - { [Op.iLike]: `%${searchQuery}%` } - ) - )), - ], - }; - - - - const hasPermission = await checkPermissions(`READ_${tableName.toUpperCase()}`, currentUser); - if (!hasPermission) { - continue; - } - - const foundRecords = await db[tableName].findAll({ - where: whereCondition, - attributes: [...tableColumns[tableName], 'id', ...attributesIntToSearch], - }); - - const modifiedRecords = foundRecords.map((record) => { - const matchAttribute = []; - - for (const attribute of attributesToSearch) { - if (record[attribute]?.toLowerCase()?.includes(searchQuery.toLowerCase())) { - matchAttribute.push(attribute); - } - } - - for (const attribute of attributesIntToSearch) { - const castedValue = String(record[attribute]); - if (castedValue && castedValue.toLowerCase().includes(searchQuery.toLowerCase())) { - matchAttribute.push(attribute); - } - } - - return { - ...record.get(), - matchAttribute, - tableName, - }; - }); - - allFoundRecords = allFoundRecords.concat(modifiedRecords); - } - } - - return allFoundRecords; - } catch (error) { - throw error; + static async search(searchQuery, currentUser) { + if (!searchQuery) { + throw new ValidationError('iam.errors.searchQueryRequired'); } + + await checkPermissions('search', currentUser); + + const [clients, sessions, actionItems, resources, packages] = await Promise.all([ + db.clients.findAll({ + where: textMatch(['name', 'email', 'company', 'role_title', 'goals', 'notes'], searchQuery), + limit: 10, + }), + db.sessions.findAll({ + where: textMatch(['title', 'ai_summary', 'key_topics', 'commitments', 'homework'], searchQuery), + limit: 10, + }), + db.action_items.findAll({ + where: textMatch(['title', 'notes'], searchQuery), + limit: 10, + }), + db.resources.findAll({ + where: textMatch(['title', 'description', 'url'], searchQuery), + limit: 10, + }), + db.packages.findAll({ + where: textMatch(['title', 'description', 'price', 'duration'], searchQuery), + limit: 10, + }), + ]); + + return [ + ...clients.map((client) => searchResult( + 'Client', + client.name, + client.email || client.company, + '/clients', + )), + ...sessions.map((session) => searchResult( + 'Session', + session.title, + session.ai_summary, + '/session-memory', + )), + ...actionItems.map((actionItem) => searchResult( + 'Action Item', + actionItem.title, + actionItem.notes, + '/dashboard', + )), + ...resources.map((resource) => searchResult( + 'Resource', + resource.title, + resource.description, + '/client-portal', + )), + ...packages.map((coachingPackage) => searchResult( + 'Package', + coachingPackage.title, + coachingPackage.description, + '/', + )), + ]; } -} \ No newline at end of file +}; diff --git a/frontend/README.md b/frontend/README.md index 9b0b60d..9b25f67 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,4 @@ -# Sales Pipeline CRM +# Coaching SaaS Workspace ## This project was generated by Flatlogic Platform. ## Install @@ -89,4 +89,3 @@ The **docker folder** contains a couple of helper scripts: 9. Stop services: 9.1. Just press `Ctr+C` - diff --git a/frontend/src/components/Accounts/CardAccounts.tsx b/frontend/src/components/Accounts/CardAccounts.tsx deleted file mode 100644 index 8be2c27..0000000 --- a/frontend/src/components/Accounts/CardAccounts.tsx +++ /dev/null @@ -1,255 +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 = { - accounts: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardAccounts = ({ - accounts, - 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_ACCOUNTS') - - - return ( -
- {loading && } - -
- -
-
- ); -}; - -export default CardAccounts; diff --git a/frontend/src/components/Accounts/ListAccounts.tsx b/frontend/src/components/Accounts/ListAccounts.tsx deleted file mode 100644 index be7fb8e..0000000 --- a/frontend/src/components/Accounts/ListAccounts.tsx +++ /dev/null @@ -1,184 +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 = { - accounts: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListAccounts = ({ accounts, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACCOUNTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && accounts.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

AccountName

-

{ item.name }

-
- - - - -
-

Domain

-

{ item.domain }

-
- - - - -
-

Industry

-

{ item.industry }

-
- - - - -
-

EmployeeCount

-

{ item.employee_count }

-
- - - - -
-

AnnualRevenue

-

{ item.annual_revenue }

-
- - - - -
-

Phone

-

{ item.phone }

-
- - - - -
-

Website

-

{ item.website }

-
- - - - -
-

Address

-

{ item.address }

-
- - - - -
-

Owner

-

{ dataFormatter.usersOneListFormatter(item.owner) }

-
- - - - -
-

AccountStatus

-

{ item.account_status }

-
- - - - -
-

LastContactedAt

-

{ dataFormatter.dateTimeFormatter(item.last_contacted_at) }

-
- - - - -
-

NextFollow-upAt

-

{ dataFormatter.dateTimeFormatter(item.next_follow_up_at) }

-
- - - - -
-

Notes

-

{ item.notes }

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

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListAccounts \ No newline at end of file diff --git a/frontend/src/components/Accounts/TableAccounts.tsx b/frontend/src/components/Accounts/TableAccounts.tsx deleted file mode 100644 index 9a34da6..0000000 --- a/frontend/src/components/Accounts/TableAccounts.tsx +++ /dev/null @@ -1,476 +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/accounts/accountsSlice' -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 "./configureAccountsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import CardAccounts from './CardAccounts'; - - -const perPage = 10 - -const TableSampleAccounts = ({ 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 { accounts, loading, count, notify: accountsNotify, refetch } = useAppSelector((state) => state.accounts) - 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 (accountsNotify.showNotification) { - notify(accountsNotify.typeNotification, accountsNotify.textNotification); - } - }, [accountsNotify.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, - `accounts`, - 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={accounts ?? []} - 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?

-
- - - {accounts && Array.isArray(accounts) && !showGrid && ( - - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleAccounts diff --git a/frontend/src/components/Accounts/configureAccountsCols.tsx b/frontend/src/components/Accounts/configureAccountsCols.tsx deleted file mode 100644 index e1413a4..0000000 --- a/frontend/src/components/Accounts/configureAccountsCols.tsx +++ /dev/null @@ -1,278 +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_ACCOUNTS') - - return [ - - { - field: 'name', - headerName: 'AccountName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'domain', - headerName: 'Domain', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'industry', - headerName: 'Industry', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'employee_count', - headerName: 'EmployeeCount', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'annual_revenue', - headerName: 'AnnualRevenue', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'phone', - headerName: 'Phone', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'website', - headerName: 'Website', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'address', - headerName: 'Address', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'owner', - headerName: 'Owner', - 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: 'account_status', - headerName: 'AccountStatus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'last_contacted_at', - headerName: 'LastContactedAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.last_contacted_at), - - }, - - { - field: 'next_follow_up_at', - headerName: 'NextFollow-upAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.next_follow_up_at), - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/frontend/src/components/Activities/CardActivities.tsx b/frontend/src/components/Activities/CardActivities.tsx deleted file mode 100644 index 695624b..0000000 --- a/frontend/src/components/Activities/CardActivities.tsx +++ /dev/null @@ -1,274 +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 = { - activities: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardActivities = ({ - activities, - 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_ACTIVITIES') - - - return ( -
- {loading && } -
    - {!loading && activities.map((item, index) => ( -
  • - -
    - - - {item.subject} - - - -
    - -
    -
    -
    - - -
    -
    ActivityType
    -
    -
    - { item.activity_type } -
    -
    -
    - - - - -
    -
    Subject
    -
    -
    - { item.subject } -
    -
    -
    - - - - -
    -
    Details
    -
    -
    - { item.details } -
    -
    -
    - - - - -
    -
    Owner
    -
    -
    - { dataFormatter.usersOneListFormatter(item.owner) } -
    -
    -
    - - - - -
    -
    Lead
    -
    -
    - { dataFormatter.leadsOneListFormatter(item.lead) } -
    -
    -
    - - - - -
    -
    Deal
    -
    -
    - { dataFormatter.dealsOneListFormatter(item.deal) } -
    -
    -
    - - - - -
    -
    Contact
    -
    -
    - { dataFormatter.contactsOneListFormatter(item.contact) } -
    -
    -
    - - - - -
    -
    Account
    -
    -
    - { dataFormatter.accountsOneListFormatter(item.account) } -
    -
    -
    - - - - -
    -
    ActivityAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.activity_at) } -
    -
    -
    - - - - -
    -
    DueAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.due_at) } -
    -
    -
    - - - - -
    -
    CompletedAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.completed_at) } -
    -
    -
    - - - - -
    -
    Status
    -
    -
    - { item.status } -
    -
    -
    - - - - -
    -
    Priority
    -
    -
    - { item.priority } -
    -
    -
    - - - - -
    -
    Attachments
    -
    -
    - {dataFormatter.filesFormatter(item.attachments).map(link => ( - - ))} -
    -
    -
    - - - -
    -
  • - ))} - {!loading && activities.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardActivities; diff --git a/frontend/src/components/Activities/ListActivities.tsx b/frontend/src/components/Activities/ListActivities.tsx deleted file mode 100644 index 829e62f..0000000 --- a/frontend/src/components/Activities/ListActivities.tsx +++ /dev/null @@ -1,199 +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 = { - activities: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListActivities = ({ activities, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACTIVITIES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && activities.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

ActivityType

-

{ item.activity_type }

-
- - - - -
-

Subject

-

{ item.subject }

-
- - - - -
-

Details

-

{ item.details }

-
- - - - -
-

Owner

-

{ dataFormatter.usersOneListFormatter(item.owner) }

-
- - - - -
-

Lead

-

{ dataFormatter.leadsOneListFormatter(item.lead) }

-
- - - - -
-

Deal

-

{ dataFormatter.dealsOneListFormatter(item.deal) }

-
- - - - -
-

Contact

-

{ dataFormatter.contactsOneListFormatter(item.contact) }

-
- - - - -
-

Account

-

{ dataFormatter.accountsOneListFormatter(item.account) }

-
- - - - -
-

ActivityAt

-

{ dataFormatter.dateTimeFormatter(item.activity_at) }

-
- - - - -
-

DueAt

-

{ dataFormatter.dateTimeFormatter(item.due_at) }

-
- - - - -
-

CompletedAt

-

{ dataFormatter.dateTimeFormatter(item.completed_at) }

-
- - - - -
-

Status

-

{ item.status }

-
- - - - -
-

Priority

-

{ item.priority }

-
- - - - -
-

Attachments

- {dataFormatter.filesFormatter(item.attachments).map(link => ( - - ))} -
- - - - - -
-
-
- ))} - {!loading && activities.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListActivities \ No newline at end of file diff --git a/frontend/src/components/Activities/TableActivities.tsx b/frontend/src/components/Activities/TableActivities.tsx deleted file mode 100644 index d08d62d..0000000 --- a/frontend/src/components/Activities/TableActivities.tsx +++ /dev/null @@ -1,489 +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/activities/activitiesSlice' -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 "./configureActivitiesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import BigCalendar from "../BigCalendar"; -import { SlotInfo } from 'react-big-calendar'; - - -const perPage = 100 - -const TableSampleActivities = ({ 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 { activities, loading, count, notify: activitiesNotify, refetch } = useAppSelector((state) => state.activities) - 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 (activitiesNotify.showNotification) { - notify(activitiesNotify.typeNotification, activitiesNotify.textNotification); - } - }, [activitiesNotify.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 handleCreateEventAction = ({ start, end }: SlotInfo) => { - router.push( - `/activities/activities-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, - ); - }; - - - 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, - `activities`, - 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={activities ?? []} - 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?

-
- - - {!showGrid && ( - { - loadData(0,`&calendarStart=${range.start}&calendarEnd=${range.end}`); - }} - entityName={'activities'} - /> - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleActivities diff --git a/frontend/src/components/Activities/configureActivitiesCols.tsx b/frontend/src/components/Activities/configureActivitiesCols.tsx deleted file mode 100644 index 7454372..0000000 --- a/frontend/src/components/Activities/configureActivitiesCols.tsx +++ /dev/null @@ -1,333 +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_ACTIVITIES') - - return [ - - { - field: 'activity_type', - headerName: 'ActivityType', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'subject', - headerName: 'Subject', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'details', - headerName: 'Details', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'owner', - headerName: 'Owner', - 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: 'lead', - headerName: 'Lead', - 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('leads'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'deal', - headerName: 'Deal', - 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('deals'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'contact', - headerName: 'Contact', - 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('contacts'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'account', - headerName: 'Account', - 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('accounts'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'activity_at', - headerName: 'ActivityAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.activity_at), - - }, - - { - field: 'due_at', - headerName: 'DueAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.due_at), - - }, - - { - field: 'completed_at', - headerName: 'CompletedAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.completed_at), - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'priority', - headerName: 'Priority', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'attachments', - headerName: 'Attachments', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - <> - {dataFormatter.filesFormatter(params.row.attachments).map(link => ( - - ))} - - ), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx index 3fc07a8..6c7a95e 100644 --- a/frontend/src/components/AsideMenuLayer.tsx +++ b/frontend/src/components/AsideMenuLayer.tsx @@ -39,7 +39,7 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props >
- Sales Pipeline CRM + Coaching SaaS Workspace
diff --git a/frontend/src/components/Contacts/CardContacts.tsx b/frontend/src/components/Contacts/CardContacts.tsx deleted file mode 100644 index 957376d..0000000 --- a/frontend/src/components/Contacts/CardContacts.tsx +++ /dev/null @@ -1,231 +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 = { - contacts: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardContacts = ({ - contacts, - 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_CONTACTS') - - - return ( -
- {loading && } -
    - {!loading && contacts.map((item, index) => ( -
  • - -
    - - - {item.full_name} - - - -
    - -
    -
    -
    - - -
    -
    FullName
    -
    -
    - { item.full_name } -
    -
    -
    - - - - -
    -
    Email
    -
    -
    - { item.email } -
    -
    -
    - - - - -
    -
    Phone
    -
    -
    - { item.phone } -
    -
    -
    - - - - -
    -
    JobTitle
    -
    -
    - { item.job_title } -
    -
    -
    - - - - -
    -
    Account
    -
    -
    - { dataFormatter.accountsOneListFormatter(item.account) } -
    -
    -
    - - - - -
    -
    Owner
    -
    -
    - { dataFormatter.usersOneListFormatter(item.owner) } -
    -
    -
    - - - - -
    -
    ContactStatus
    -
    -
    - { item.contact_status } -
    -
    -
    - - - - -
    -
    LinkedInURL
    -
    -
    - { item.linkedin_url } -
    -
    -
    - - - - -
    -
    Notes
    -
    -
    - { item.notes } -
    -
    -
    - - - - -
    -
    LastContactedAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.last_contacted_at) } -
    -
    -
    - - - - -
    -
    NextFollow-upAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.next_follow_up_at) } -
    -
    -
    - - - -
    -
  • - ))} - {!loading && contacts.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardContacts; diff --git a/frontend/src/components/Contacts/ListContacts.tsx b/frontend/src/components/Contacts/ListContacts.tsx deleted file mode 100644 index ae245ae..0000000 --- a/frontend/src/components/Contacts/ListContacts.tsx +++ /dev/null @@ -1,168 +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 = { - contacts: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListContacts = ({ contacts, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CONTACTS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && contacts.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

FullName

-

{ item.full_name }

-
- - - - -
-

Email

-

{ item.email }

-
- - - - -
-

Phone

-

{ item.phone }

-
- - - - -
-

JobTitle

-

{ item.job_title }

-
- - - - -
-

Account

-

{ dataFormatter.accountsOneListFormatter(item.account) }

-
- - - - -
-

Owner

-

{ dataFormatter.usersOneListFormatter(item.owner) }

-
- - - - -
-

ContactStatus

-

{ item.contact_status }

-
- - - - -
-

LinkedInURL

-

{ item.linkedin_url }

-
- - - - -
-

Notes

-

{ item.notes }

-
- - - - -
-

LastContactedAt

-

{ dataFormatter.dateTimeFormatter(item.last_contacted_at) }

-
- - - - -
-

NextFollow-upAt

-

{ dataFormatter.dateTimeFormatter(item.next_follow_up_at) }

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

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListContacts \ No newline at end of file diff --git a/frontend/src/components/Contacts/TableContacts.tsx b/frontend/src/components/Contacts/TableContacts.tsx deleted file mode 100644 index 2cb1152..0000000 --- a/frontend/src/components/Contacts/TableContacts.tsx +++ /dev/null @@ -1,463 +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/contacts/contactsSlice' -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 "./configureContactsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleContacts = ({ 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 { contacts, loading, count, notify: contactsNotify, refetch } = useAppSelector((state) => state.contacts) - 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 (contactsNotify.showNotification) { - notify(contactsNotify.typeNotification, contactsNotify.textNotification); - } - }, [contactsNotify.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, - `contacts`, - 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={contacts ?? []} - 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 TableSampleContacts diff --git a/frontend/src/components/Contacts/configureContactsCols.tsx b/frontend/src/components/Contacts/configureContactsCols.tsx deleted file mode 100644 index dfb1494..0000000 --- a/frontend/src/components/Contacts/configureContactsCols.tsx +++ /dev/null @@ -1,253 +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_CONTACTS') - - return [ - - { - field: 'full_name', - headerName: 'FullName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'email', - headerName: 'Email', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'phone', - headerName: 'Phone', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'job_title', - headerName: 'JobTitle', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'account', - headerName: 'Account', - 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('accounts'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'owner', - headerName: 'Owner', - 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: 'contact_status', - headerName: 'ContactStatus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'linkedin_url', - headerName: 'LinkedInURL', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'last_contacted_at', - headerName: 'LastContactedAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.last_contacted_at), - - }, - - { - field: 'next_follow_up_at', - headerName: 'NextFollow-upAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.next_follow_up_at), - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/frontend/src/components/Deals/CardDeals.tsx b/frontend/src/components/Deals/CardDeals.tsx deleted file mode 100644 index fb32625..0000000 --- a/frontend/src/components/Deals/CardDeals.tsx +++ /dev/null @@ -1,279 +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 = { - deals: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardDeals = ({ - deals, - 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_DEALS') - - - return ( -
- {loading && } -
    - {!loading && deals.map((item, index) => ( -
  • - -
    - - - {item.name} - - - -
    - -
    -
    -
    - - -
    -
    DealName
    -
    -
    - { item.name } -
    -
    -
    - - - - -
    -
    Stage
    -
    -
    - { dataFormatter.pipeline_stagesOneListFormatter(item.stage) } -
    -
    -
    - - - - -
    -
    Owner
    -
    -
    - { dataFormatter.usersOneListFormatter(item.owner) } -
    -
    -
    - - - - -
    -
    Account
    -
    -
    - { dataFormatter.accountsOneListFormatter(item.account) } -
    -
    -
    - - - - -
    -
    PrimaryContact
    -
    -
    - { dataFormatter.contactsOneListFormatter(item.primary_contact) } -
    -
    -
    - - - - -
    -
    OriginatingLead
    -
    -
    - { dataFormatter.leadsOneListFormatter(item.originating_lead) } -
    -
    -
    - - - - -
    -
    Amount
    -
    -
    - { item.amount } -
    -
    -
    - - - - -
    -
    Currency
    -
    -
    - { item.currency } -
    -
    -
    - - - - -
    -
    ExpectedCloseDate
    -
    -
    - { dataFormatter.dateTimeFormatter(item.expected_close_at) } -
    -
    -
    - - - - -
    -
    ClosedDate
    -
    -
    - { dataFormatter.dateTimeFormatter(item.closed_at) } -
    -
    -
    - - - - -
    -
    DealStatus
    -
    -
    - { item.deal_status } -
    -
    -
    - - - - -
    -
    LossReason
    -
    -
    - { item.loss_reason } -
    -
    -
    - - - - -
    -
    LastActivityAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.last_activity_at) } -
    -
    -
    - - - - -
    -
    NextFollow-upAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.next_follow_up_at) } -
    -
    -
    - - - - -
    -
    Description
    -
    -
    - { item.description } -
    -
    -
    - - - -
    -
  • - ))} - {!loading && deals.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardDeals; diff --git a/frontend/src/components/Deals/ListDeals.tsx b/frontend/src/components/Deals/ListDeals.tsx deleted file mode 100644 index 6c9a675..0000000 --- a/frontend/src/components/Deals/ListDeals.tsx +++ /dev/null @@ -1,200 +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 = { - deals: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListDeals = ({ deals, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_DEALS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && deals.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

DealName

-

{ item.name }

-
- - - - -
-

Stage

-

{ dataFormatter.pipeline_stagesOneListFormatter(item.stage) }

-
- - - - -
-

Owner

-

{ dataFormatter.usersOneListFormatter(item.owner) }

-
- - - - -
-

Account

-

{ dataFormatter.accountsOneListFormatter(item.account) }

-
- - - - -
-

PrimaryContact

-

{ dataFormatter.contactsOneListFormatter(item.primary_contact) }

-
- - - - -
-

OriginatingLead

-

{ dataFormatter.leadsOneListFormatter(item.originating_lead) }

-
- - - - -
-

Amount

-

{ item.amount }

-
- - - - -
-

Currency

-

{ item.currency }

-
- - - - -
-

ExpectedCloseDate

-

{ dataFormatter.dateTimeFormatter(item.expected_close_at) }

-
- - - - -
-

ClosedDate

-

{ dataFormatter.dateTimeFormatter(item.closed_at) }

-
- - - - -
-

DealStatus

-

{ item.deal_status }

-
- - - - -
-

LossReason

-

{ item.loss_reason }

-
- - - - -
-

LastActivityAt

-

{ dataFormatter.dateTimeFormatter(item.last_activity_at) }

-
- - - - -
-

NextFollow-upAt

-

{ dataFormatter.dateTimeFormatter(item.next_follow_up_at) }

-
- - - - -
-

Description

-

{ item.description }

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

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListDeals \ No newline at end of file diff --git a/frontend/src/components/Deals/TableDeals.tsx b/frontend/src/components/Deals/TableDeals.tsx deleted file mode 100644 index e6b860b..0000000 --- a/frontend/src/components/Deals/TableDeals.tsx +++ /dev/null @@ -1,506 +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/deals/dealsSlice' -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 "./configureDealsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import KanbanBoard from '../KanbanBoard/KanbanBoard'; -import axios from 'axios'; - - -const perPage = 10 - -const TableSampleDeals = ({ 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 [kanbanColumns, setKanbanColumns] = useState | null>(null); - const [kanbanFilters, setKanbanFilters] = useState(''); - - const { deals, loading, count, notify: dealsNotify, refetch } = useAppSelector((state) => state.deals) - 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 (dealsNotify.showNotification) { - notify(dealsNotify.typeNotification, dealsNotify.textNotification); - } - }, [dealsNotify.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) - } - - - useEffect(() => { - - - - - - axios.get('/pipeline_stages/autocomplete?limit=100') - .then((res) => { - setKanbanColumns(res.data); - }) - .catch((err) => { - console.error('Error fetching kanban columns:', err); - }); - - - - }, []); - - - - - 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, ''); - - setKanbanFilters(''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - - setKanbanFilters(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, ''); - - setKanbanFilters(''); - - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - - useEffect(() => { - if (!currentUser) return; - - loadColumns( - handleDeleteModalAction, - `deals`, - 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={deals ?? []} - 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?

-
- - - - {!showGrid && kanbanColumns && ( - - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleDeals diff --git a/frontend/src/components/Deals/configureDealsCols.tsx b/frontend/src/components/Deals/configureDealsCols.tsx deleted file mode 100644 index 4610be6..0000000 --- a/frontend/src/components/Deals/configureDealsCols.tsx +++ /dev/null @@ -1,341 +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_DEALS') - - return [ - - { - field: 'name', - headerName: 'DealName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'stage', - headerName: 'Stage', - 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('pipeline_stages'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'owner', - headerName: 'Owner', - 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: 'account', - headerName: 'Account', - 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('accounts'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'primary_contact', - headerName: 'PrimaryContact', - 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('contacts'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'originating_lead', - headerName: 'OriginatingLead', - 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('leads'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'amount', - headerName: 'Amount', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'currency', - headerName: 'Currency', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'expected_close_at', - headerName: 'ExpectedCloseDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.expected_close_at), - - }, - - { - field: 'closed_at', - headerName: 'ClosedDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.closed_at), - - }, - - { - field: 'deal_status', - headerName: 'DealStatus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'loss_reason', - headerName: 'LossReason', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'last_activity_at', - headerName: 'LastActivityAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.last_activity_at), - - }, - - { - field: 'next_follow_up_at', - headerName: 'NextFollow-upAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.next_follow_up_at), - - }, - - { - field: 'description', - headerName: 'Description', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/frontend/src/components/Lead_sources/CardLead_sources.tsx b/frontend/src/components/Lead_sources/CardLead_sources.tsx deleted file mode 100644 index e161922..0000000 --- a/frontend/src/components/Lead_sources/CardLead_sources.tsx +++ /dev/null @@ -1,135 +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 = { - lead_sources: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardLead_sources = ({ - lead_sources, - 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_LEAD_SOURCES') - - - return ( -
- {loading && } -
    - {!loading && lead_sources.map((item, index) => ( -
  • - -
    - - - {item.name} - - - -
    - -
    -
    -
    - - -
    -
    SourceName
    -
    -
    - { item.name } -
    -
    -
    - - - - -
    -
    Active
    -
    -
    - { dataFormatter.booleanFormatter(item.is_active) } -
    -
    -
    - - - - -
    -
    Description
    -
    -
    - { item.description } -
    -
    -
    - - - -
    -
  • - ))} - {!loading && lead_sources.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardLead_sources; diff --git a/frontend/src/components/Lead_sources/ListLead_sources.tsx b/frontend/src/components/Lead_sources/ListLead_sources.tsx deleted file mode 100644 index 2aeaa2b..0000000 --- a/frontend/src/components/Lead_sources/ListLead_sources.tsx +++ /dev/null @@ -1,104 +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 = { - lead_sources: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListLead_sources = ({ lead_sources, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LEAD_SOURCES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && lead_sources.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

SourceName

-

{ item.name }

-
- - - - -
-

Active

-

{ dataFormatter.booleanFormatter(item.is_active) }

-
- - - - -
-

Description

-

{ item.description }

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

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListLead_sources \ No newline at end of file diff --git a/frontend/src/components/Lead_sources/TableLead_sources.tsx b/frontend/src/components/Lead_sources/TableLead_sources.tsx deleted file mode 100644 index 407fcb8..0000000 --- a/frontend/src/components/Lead_sources/TableLead_sources.tsx +++ /dev/null @@ -1,476 +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/lead_sources/lead_sourcesSlice' -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 "./configureLead_sourcesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import ListLead_sources from './ListLead_sources'; - - -const perPage = 10 - -const TableSampleLead_sources = ({ 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 { lead_sources, loading, count, notify: lead_sourcesNotify, refetch } = useAppSelector((state) => state.lead_sources) - 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 (lead_sourcesNotify.showNotification) { - notify(lead_sourcesNotify.typeNotification, lead_sourcesNotify.textNotification); - } - }, [lead_sourcesNotify.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, - `lead_sources`, - 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={lead_sources ?? []} - 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?

-
- - - {lead_sources && Array.isArray(lead_sources) && !showGrid && ( - - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSampleLead_sources diff --git a/frontend/src/components/Lead_sources/configureLead_sourcesCols.tsx b/frontend/src/components/Lead_sources/configureLead_sourcesCols.tsx deleted file mode 100644 index 69fdc76..0000000 --- a/frontend/src/components/Lead_sources/configureLead_sourcesCols.tsx +++ /dev/null @@ -1,114 +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_LEAD_SOURCES') - - return [ - - { - field: 'name', - headerName: 'SourceName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'is_active', - headerName: 'Active', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'description', - headerName: 'Description', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/frontend/src/components/Leads/CardLeads.tsx b/frontend/src/components/Leads/CardLeads.tsx deleted file mode 100644 index 3279cb9..0000000 --- a/frontend/src/components/Leads/CardLeads.tsx +++ /dev/null @@ -1,255 +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 = { - leads: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardLeads = ({ - leads, - 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_LEADS') - - - return ( -
- {loading && } -
    - {!loading && leads.map((item, index) => ( -
  • - -
    - - - {item.lead_name} - - - -
    - -
    -
    -
    - - -
    -
    LeadName
    -
    -
    - { item.lead_name } -
    -
    -
    - - - - -
    -
    CompanyName
    -
    -
    - { item.company_name } -
    -
    -
    - - - - -
    -
    Email
    -
    -
    - { item.email } -
    -
    -
    - - - - -
    -
    Phone
    -
    -
    - { item.phone } -
    -
    -
    - - - - -
    -
    Owner
    -
    -
    - { dataFormatter.usersOneListFormatter(item.owner) } -
    -
    -
    - - - - -
    -
    Source
    -
    -
    - { dataFormatter.lead_sourcesOneListFormatter(item.source) } -
    -
    -
    - - - - -
    -
    LeadStatus
    -
    -
    - { item.lead_status } -
    -
    -
    - - - - -
    -
    LeadRating
    -
    -
    - { item.lead_rating } -
    -
    -
    - - - - -
    -
    Notes
    -
    -
    - { item.notes } -
    -
    -
    - - - - -
    -
    LastContactedAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.last_contacted_at) } -
    -
    -
    - - - - -
    -
    NextFollow-upAt
    -
    -
    - { dataFormatter.dateTimeFormatter(item.next_follow_up_at) } -
    -
    -
    - - - - -
    -
    LinkedAccount
    -
    -
    - { dataFormatter.accountsOneListFormatter(item.account) } -
    -
    -
    - - - - -
    -
    PrimaryContact
    -
    -
    - { dataFormatter.contactsOneListFormatter(item.primary_contact) } -
    -
    -
    - - - -
    -
  • - ))} - {!loading && leads.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardLeads; diff --git a/frontend/src/components/Leads/ListLeads.tsx b/frontend/src/components/Leads/ListLeads.tsx deleted file mode 100644 index cd26365..0000000 --- a/frontend/src/components/Leads/ListLeads.tsx +++ /dev/null @@ -1,184 +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 = { - leads: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListLeads = ({ leads, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LEADS') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && leads.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

LeadName

-

{ item.lead_name }

-
- - - - -
-

CompanyName

-

{ item.company_name }

-
- - - - -
-

Email

-

{ item.email }

-
- - - - -
-

Phone

-

{ item.phone }

-
- - - - -
-

Owner

-

{ dataFormatter.usersOneListFormatter(item.owner) }

-
- - - - -
-

Source

-

{ dataFormatter.lead_sourcesOneListFormatter(item.source) }

-
- - - - -
-

LeadStatus

-

{ item.lead_status }

-
- - - - -
-

LeadRating

-

{ item.lead_rating }

-
- - - - -
-

Notes

-

{ item.notes }

-
- - - - -
-

LastContactedAt

-

{ dataFormatter.dateTimeFormatter(item.last_contacted_at) }

-
- - - - -
-

NextFollow-upAt

-

{ dataFormatter.dateTimeFormatter(item.next_follow_up_at) }

-
- - - - -
-

LinkedAccount

-

{ dataFormatter.accountsOneListFormatter(item.account) }

-
- - - - -
-

PrimaryContact

-

{ dataFormatter.contactsOneListFormatter(item.primary_contact) }

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

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListLeads \ No newline at end of file diff --git a/frontend/src/components/Leads/TableLeads.tsx b/frontend/src/components/Leads/TableLeads.tsx deleted file mode 100644 index de522f1..0000000 --- a/frontend/src/components/Leads/TableLeads.tsx +++ /dev/null @@ -1,463 +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/leads/leadsSlice' -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 "./configureLeadsCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - - -const perPage = 10 - -const TableSampleLeads = ({ 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 { leads, loading, count, notify: leadsNotify, refetch } = useAppSelector((state) => state.leads) - 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 (leadsNotify.showNotification) { - notify(leadsNotify.typeNotification, leadsNotify.textNotification); - } - }, [leadsNotify.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, - `leads`, - 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={leads ?? []} - 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 TableSampleLeads diff --git a/frontend/src/components/Leads/configureLeadsCols.tsx b/frontend/src/components/Leads/configureLeadsCols.tsx deleted file mode 100644 index eb91efa..0000000 --- a/frontend/src/components/Leads/configureLeadsCols.tsx +++ /dev/null @@ -1,297 +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_LEADS') - - return [ - - { - field: 'lead_name', - headerName: 'LeadName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'company_name', - headerName: 'CompanyName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'email', - headerName: 'Email', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'phone', - headerName: 'Phone', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'owner', - headerName: 'Owner', - 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: 'source', - headerName: 'Source', - 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('lead_sources'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'lead_status', - headerName: 'LeadStatus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'lead_rating', - headerName: 'LeadRating', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'last_contacted_at', - headerName: 'LastContactedAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.last_contacted_at), - - }, - - { - field: 'next_follow_up_at', - headerName: 'NextFollow-upAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.next_follow_up_at), - - }, - - { - field: 'account', - headerName: 'LinkedAccount', - 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('accounts'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'primary_contact', - headerName: 'PrimaryContact', - 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('contacts'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/frontend/src/components/Pipeline_stages/CardPipeline_stages.tsx b/frontend/src/components/Pipeline_stages/CardPipeline_stages.tsx deleted file mode 100644 index d46edfc..0000000 --- a/frontend/src/components/Pipeline_stages/CardPipeline_stages.tsx +++ /dev/null @@ -1,171 +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 = { - pipeline_stages: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardPipeline_stages = ({ - pipeline_stages, - 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_PIPELINE_STAGES') - - - return ( -
- {loading && } -
    - {!loading && pipeline_stages.map((item, index) => ( -
  • - -
    - - - {item.name} - - - -
    - -
    -
    -
    - - -
    -
    StageName
    -
    -
    - { item.name } -
    -
    -
    - - - - -
    -
    SortOrder
    -
    -
    - { item.sort_order } -
    -
    -
    - - - - -
    -
    WinProbability
    -
    -
    - { item.win_probability } -
    -
    -
    - - - - -
    -
    IsWon
    -
    -
    - { dataFormatter.booleanFormatter(item.is_won) } -
    -
    -
    - - - - -
    -
    IsLost
    -
    -
    - { dataFormatter.booleanFormatter(item.is_lost) } -
    -
    -
    - - - - -
    -
    Active
    -
    -
    - { dataFormatter.booleanFormatter(item.is_active) } -
    -
    -
    - - - -
    -
  • - ))} - {!loading && pipeline_stages.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardPipeline_stages; diff --git a/frontend/src/components/Pipeline_stages/ListPipeline_stages.tsx b/frontend/src/components/Pipeline_stages/ListPipeline_stages.tsx deleted file mode 100644 index 804fca7..0000000 --- a/frontend/src/components/Pipeline_stages/ListPipeline_stages.tsx +++ /dev/null @@ -1,128 +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 = { - pipeline_stages: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListPipeline_stages = ({ pipeline_stages, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { - - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PIPELINE_STAGES') - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - - return ( - <> -
- {loading && } - {!loading && pipeline_stages.map((item) => ( -
- -
- - dark:divide-dark-700 overflow-x-auto' - } - > - - -
-

StageName

-

{ item.name }

-
- - - - -
-

SortOrder

-

{ item.sort_order }

-
- - - - -
-

WinProbability

-

{ item.win_probability }

-
- - - - -
-

IsWon

-

{ dataFormatter.booleanFormatter(item.is_won) }

-
- - - - -
-

IsLost

-

{ dataFormatter.booleanFormatter(item.is_lost) }

-
- - - - -
-

Active

-

{ dataFormatter.booleanFormatter(item.is_active) }

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

No data to display

-
- )} -
-
- -
- - ) -}; - -export default ListPipeline_stages \ No newline at end of file diff --git a/frontend/src/components/Pipeline_stages/TablePipeline_stages.tsx b/frontend/src/components/Pipeline_stages/TablePipeline_stages.tsx deleted file mode 100644 index 8c04ee0..0000000 --- a/frontend/src/components/Pipeline_stages/TablePipeline_stages.tsx +++ /dev/null @@ -1,476 +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/pipeline_stages/pipeline_stagesSlice' -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 "./configurePipeline_stagesCols"; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter' -import {dataGridStyles} from "../../styles"; - - -import ListPipeline_stages from './ListPipeline_stages'; - - -const perPage = 10 - -const TableSamplePipeline_stages = ({ 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 { pipeline_stages, loading, count, notify: pipeline_stagesNotify, refetch } = useAppSelector((state) => state.pipeline_stages) - 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 (pipeline_stagesNotify.showNotification) { - notify(pipeline_stagesNotify.typeNotification, pipeline_stagesNotify.textNotification); - } - }, [pipeline_stagesNotify.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, - `pipeline_stages`, - 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={pipeline_stages ?? []} - 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?

-
- - - {pipeline_stages && Array.isArray(pipeline_stages) && !showGrid && ( - - )} - - - - {showGrid && dataGrid} - - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ) -} - -export default TableSamplePipeline_stages diff --git a/frontend/src/components/Pipeline_stages/configurePipeline_stagesCols.tsx b/frontend/src/components/Pipeline_stages/configurePipeline_stagesCols.tsx deleted file mode 100644 index ec9bd69..0000000 --- a/frontend/src/components/Pipeline_stages/configurePipeline_stagesCols.tsx +++ /dev/null @@ -1,163 +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_PIPELINE_STAGES') - - return [ - - { - field: 'name', - headerName: 'StageName', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'sort_order', - headerName: 'SortOrder', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'win_probability', - headerName: 'WinProbability', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'is_won', - headerName: 'IsWon', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'is_lost', - headerName: 'IsLost', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'is_active', - headerName: 'Active', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - - return [ -
- -
, - ] - }, - }, - ]; -}; diff --git a/frontend/src/helpers/dataFormatter.js b/frontend/src/helpers/dataFormatter.js index 4504fd3..b6baeb7 100644 --- a/frontend/src/helpers/dataFormatter.js +++ b/frontend/src/helpers/dataFormatter.js @@ -100,134 +100,4 @@ export default { if (!val) return '' return {label: val.name, id: val.id} }, - - - - accountsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - accountsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - accountsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - accountsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - contactsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.full_name) - }, - contactsOneListFormatter(val) { - if (!val) return '' - return val.full_name - }, - contactsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.full_name} - }); - }, - contactsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.full_name, id: val.id} - }, - - - - lead_sourcesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - lead_sourcesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - lead_sourcesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - lead_sourcesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - leadsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.lead_name) - }, - leadsOneListFormatter(val) { - if (!val) return '' - return val.lead_name - }, - leadsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.lead_name} - }); - }, - leadsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.lead_name, id: val.id} - }, - - - - pipeline_stagesManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - pipeline_stagesOneListFormatter(val) { - if (!val) return '' - return val.name - }, - pipeline_stagesManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - pipeline_stagesOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - dealsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.name) - }, - dealsOneListFormatter(val) { - if (!val) return '' - return val.name - }, - dealsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.name} - }); - }, - dealsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.name, id: val.id} - }, - - - - } diff --git a/frontend/src/pages/accounts/[accountsId].tsx b/frontend/src/pages/accounts/[accountsId].tsx deleted file mode 100644 index 4c68924..0000000 --- a/frontend/src/pages/accounts/[accountsId].tsx +++ /dev/null @@ -1,1007 +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/accounts/accountsSlice' -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 EditAccounts = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const initVals = { - - - 'name': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'domain': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'industry': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - employee_count: '', - - - - - - - - - - - - - - - - - - - - - - 'annual_revenue': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'phone': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'website': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - address: '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - owner: null, - - - - - - - - - - - - - - - - - - - - - - account_status: '', - - - - - - - - - - - - - - - - - - - - - - last_contacted_at: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - next_follow_up_at: new Date(), - - - - - - - - - - - - - - - - - - - - - - notes: '', - - - - - - - - - - - - - - - - - - - - - - - - } - const [initialValues, setInitialValues] = useState(initVals) - - const { accounts } = useAppSelector((state) => state.accounts) - - - const { accountsId } = router.query - - useEffect(() => { - dispatch(fetch({ id: accountsId })) - }, [accountsId]) - - useEffect(() => { - if (typeof accounts === 'object') { - setInitialValues(accounts) - } - }, [accounts]) - - useEffect(() => { - if (typeof accounts === 'object') { - - const newInitialVal = {...initVals}; - - Object.keys(initVals).forEach(el => newInitialVal[el] = (accounts)[el]) - - setInitialValues(newInitialVal); - } - }, [accounts]) - - const handleSubmit = async (data) => { - await dispatch(update({ id: accountsId, data })) - await router.push('/accounts/accounts-list') - } - - return ( - <> - - {getPageTitle('Edit accounts')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'last_contacted_at': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'next_follow_up_at': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/accounts/accounts-list')}/> - - -
-
-
- - ) -} - -EditAccounts.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default EditAccounts diff --git a/frontend/src/pages/accounts/accounts-edit.tsx b/frontend/src/pages/accounts/accounts-edit.tsx deleted file mode 100644 index 2cb1d2f..0000000 --- a/frontend/src/pages/accounts/accounts-edit.tsx +++ /dev/null @@ -1,1004 +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/accounts/accountsSlice' -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 EditAccountsPage = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const initVals = { - - - 'name': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'domain': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'industry': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - employee_count: '', - - - - - - - - - - - - - - - - - - - - - - 'annual_revenue': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'phone': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - 'website': '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - address: '', - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - owner: null, - - - - - - - - - - - - - - - - - - - - - - account_status: '', - - - - - - - - - - - - - - - - - - - - - - last_contacted_at: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - next_follow_up_at: new Date(), - - - - - - - - - - - - - - - - - - - - - - notes: '', - - - - - - - - - - - - - - - - - - - - - - - - } - const [initialValues, setInitialValues] = useState(initVals) - - const { accounts } = useAppSelector((state) => state.accounts) - - - const { id } = router.query - - useEffect(() => { - dispatch(fetch({ id: id })) - }, [id]) - - useEffect(() => { - if (typeof accounts === 'object') { - setInitialValues(accounts) - } - }, [accounts]) - - useEffect(() => { - if (typeof accounts === 'object') { - const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (accounts)[el]) - setInitialValues(newInitialVal); - } - }, [accounts]) - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })) - await router.push('/accounts/accounts-list') - } - - return ( - <> - - {getPageTitle('Edit accounts')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'last_contacted_at': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'next_follow_up_at': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/accounts/accounts-list')}/> - - -
-
-
- - ) -} - -EditAccountsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default EditAccountsPage diff --git a/frontend/src/pages/accounts/accounts-list.tsx b/frontend/src/pages/accounts/accounts-list.tsx deleted file mode 100644 index 46592a7..0000000 --- a/frontend/src/pages/accounts/accounts-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 TableAccounts from '../../components/Accounts/TableAccounts' -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/accounts/accountsSlice'; - - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const AccountsTablesPage = () => { - 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: 'AccountName', title: 'name'},{label: 'Domain', title: 'domain'},{label: 'Industry', title: 'industry'},{label: 'Phone', title: 'phone'},{label: 'Website', title: 'website'},{label: 'Address', title: 'address'},{label: 'Notes', title: 'notes'}, - {label: 'EmployeeCount', title: 'employee_count', number: 'true'}, - {label: 'AnnualRevenue', title: 'annual_revenue', number: 'true'}, - {label: 'LastContactedAt', title: 'last_contacted_at', date: 'true'},{label: 'NextFollow-upAt', title: 'next_follow_up_at', date: 'true'}, - - - {label: 'Owner', title: 'owner'}, - - - - {label: 'AccountStatus', title: 'account_status', type: 'enum', options: ['prospect','active_customer','former_customer','partner']}, - ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ACCOUNTS'); - - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAccountsCSV = async () => { - const response = await axios({url: '/accounts?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 = 'accountsCSV.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('Accounts')} - - - - {''} - - - - {hasCreatePermission && } - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
- -
- Switch to Table -
- -
- - - - - -
- - - - - ) -} - -AccountsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default AccountsTablesPage diff --git a/frontend/src/pages/accounts/accounts-new.tsx b/frontend/src/pages/accounts/accounts-new.tsx deleted file mode 100644 index bf33cc1..0000000 --- a/frontend/src/pages/accounts/accounts-new.tsx +++ /dev/null @@ -1,754 +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/accounts/accountsSlice' -import { useAppDispatch } from '../../stores/hooks' -import { useRouter } from 'next/router' -import moment from 'moment'; - -const initialValues = { - - - name: '', - - - - - - - - - - - - - - - - domain: '', - - - - - - - - - - - - - - - - industry: '', - - - - - - - - - - - - - - - - - - - employee_count: '', - - - - - - - - - - - - - annual_revenue: '', - - - - - - - - - - - - - - - - phone: '', - - - - - - - - - - - - - - - - website: '', - - - - - - - - - - - - - - - - - address: '', - - - - - - - - - - - - - - - - - - - - - - - - - - owner: '', - - - - - - - - - - - - - - account_status: 'prospect', - - - - - - - - - - - - - last_contacted_at: '', - - - - - - - - - - - - - - - - next_follow_up_at: '', - - - - - - - - - - - - - notes: '', - - - - - - - - - - - - -} - - -const AccountsNew = () => { - const router = useRouter() - const dispatch = useAppDispatch() - - - - - const handleSubmit = async (data) => { - await dispatch(create(data)) - await router.push('/accounts/accounts-list') - } - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/accounts/accounts-list')}/> - - -
-
-
- - ) -} - -AccountsNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default AccountsNew diff --git a/frontend/src/pages/accounts/accounts-table.tsx b/frontend/src/pages/accounts/accounts-table.tsx deleted file mode 100644 index b78e670..0000000 --- a/frontend/src/pages/accounts/accounts-table.tsx +++ /dev/null @@ -1,168 +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 TableAccounts from '../../components/Accounts/TableAccounts' -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/accounts/accountsSlice'; - - -import {hasPermission} from "../../helpers/userPermissions"; - - - -const AccountsTablesPage = () => { - 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: 'AccountName', title: 'name'},{label: 'Domain', title: 'domain'},{label: 'Industry', title: 'industry'},{label: 'Phone', title: 'phone'},{label: 'Website', title: 'website'},{label: 'Address', title: 'address'},{label: 'Notes', title: 'notes'}, - {label: 'EmployeeCount', title: 'employee_count', number: 'true'}, - {label: 'AnnualRevenue', title: 'annual_revenue', number: 'true'}, - {label: 'LastContactedAt', title: 'last_contacted_at', date: 'true'},{label: 'NextFollow-upAt', title: 'next_follow_up_at', date: 'true'}, - - - {label: 'Owner', title: 'owner'}, - - - - {label: 'AccountStatus', title: 'account_status', type: 'enum', options: ['prospect','active_customer','former_customer','partner']}, - ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ACCOUNTS'); - - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAccountsCSV = async () => { - const response = await axios({url: '/accounts?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 = 'accountsCSV.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('Accounts')} - - - - {''} - - - - {hasCreatePermission && } - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
- - - Back to card - - -
-
- - - -
- - - - - ) -} - -AccountsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ) -} - -export default AccountsTablesPage diff --git a/frontend/src/pages/accounts/accounts-view.tsx b/frontend/src/pages/accounts/accounts-view.tsx deleted file mode 100644 index e853732..0000000 --- a/frontend/src/pages/accounts/accounts-view.tsx +++ /dev/null @@ -1,1065 +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/accounts/accountsSlice' -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 AccountsView = () => { - const router = useRouter() - const dispatch = useAppDispatch() - const { accounts } = useAppSelector((state) => state.accounts) - - - 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 accounts')} - - - - - - - - - -
-

AccountName

-

{accounts?.name}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Domain

-

{accounts?.domain}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Industry

-

{accounts?.industry}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

EmployeeCount

-

{accounts?.employee_count || 'No data'}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

AnnualRevenue

-

{accounts?.annual_revenue || 'No data'}

-
- - - - - - - - - - - - - - - - - - - - - - -
-

Phone

-

{accounts?.phone}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

Website

-

{accounts?.website}

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -