From d4a5e6b45c6a3a017e75ef1e18b0da85cd116790 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 23 Jan 2026 12:14:34 +0000 Subject: [PATCH] v1 --- .gitignore | 5 +- 502.html | 4 +- README.md | 2 +- backend/.env | 6 +- backend/README.md | 6 +- backend/package.json | 4 +- backend/src/config.js | 14 +- .../src/db/api/{progress.js => activities.js} | 305 ++- backend/src/db/api/contacts.js | 495 ++++ backend/src/db/api/{lessons.js => deals.js} | 365 +-- .../src/db/api/{enrollments.js => leads.js} | 296 +- backend/src/db/api/permissions.js | 1 + .../db/api/{courses.js => pipeline_stages.js} | 327 +-- backend/src/db/api/roles.js | 1 + backend/src/db/api/users.js | 15 +- backend/src/db/db.config.js | 2 +- .../{1768904423064.js => 1769169079630.js} | 1118 +++++--- .../models/{enrollments.js => activities.js} | 87 +- backend/src/db/models/contacts.js | 124 + .../src/db/models/{lessons.js => deals.js} | 98 +- .../src/db/models/{courses.js => leads.js} | 119 +- backend/src/db/models/permissions.js | 1 + .../{progress.js => pipeline_stages.js} | 76 +- backend/src/db/models/roles.js | 1 + backend/src/db/models/users.js | 31 +- .../db/seeders/20200430130760-user-roles.js | 434 +-- .../db/seeders/20231127130745-sample-data.js | 2424 ++++++++++++----- backend/src/index.js | 29 +- .../src/routes/{lessons.js => activities.js} | 126 +- .../src/routes/{progress.js => contacts.js} | 139 +- backend/src/routes/deals.js | 461 ++++ backend/src/routes/{courses.js => leads.js} | 132 +- .../{enrollments.js => pipeline_stages.js} | 123 +- backend/src/routes/sql.js | 61 + .../{enrollments.js => activities.js} | 22 +- .../src/services/{progress.js => contacts.js} | 22 +- backend/src/services/{courses.js => deals.js} | 22 +- backend/src/services/{lessons.js => leads.js} | 22 +- backend/src/services/notifications/list.js | 2 +- backend/src/services/pipeline_stages.js | 138 + backend/src/services/search.js | 121 +- docker/docker-compose.yml | 2 +- frontend/README.md | 2 +- .../CardActivities.tsx} | 101 +- .../ListActivities.tsx} | 68 +- .../TableActivities.tsx} | 36 +- .../configureActivitiesCols.tsx} | 192 +- frontend/src/components/AsideMenuLayer.tsx | 2 +- .../CardContacts.tsx} | 50 +- .../ListContacts.tsx} | 46 +- .../TableContacts.tsx} | 28 +- .../configureContactsCols.tsx} | 153 +- .../CardLessons.tsx => Deals/CardDeals.tsx} | 134 +- .../ListCourses.tsx => Deals/ListDeals.tsx} | 165 +- .../TableLessons.tsx => Deals/TableDeals.tsx} | 45 +- .../configureDealsCols.tsx} | 215 +- .../CardLeads.tsx} | 70 +- .../ListLeads.tsx} | 58 +- .../TableLeads.tsx} | 20 +- .../configureLeadsCols.tsx} | 155 +- frontend/src/components/NavBarItem.tsx | 5 +- .../Pipeline_stages/CardPipeline_stages.tsx | 147 + .../Pipeline_stages/ListPipeline_stages.tsx | 112 + .../Pipeline_stages/TablePipeline_stages.tsx | 463 ++++ .../configurePipeline_stagesCols.tsx | 131 + frontend/src/helpers/dataFormatter.js | 79 +- frontend/src/layouts/Authenticated.tsx | 5 +- frontend/src/menuAside.ts | 40 +- frontend/src/pages/_app.tsx | 6 +- .../[activitiesId].tsx} | 597 ++-- .../activities-edit.tsx} | 589 ++-- .../activities-list.tsx} | 50 +- .../activities-new.tsx} | 328 +-- .../activities-table.tsx} | 50 +- .../activities-view.tsx} | 375 ++- .../[contactsId].tsx} | 530 ++-- .../contacts-edit.tsx} | 522 ++-- .../contacts-list.tsx} | 46 +- .../contacts-new.tsx} | 387 +-- .../contacts-table.tsx} | 46 +- .../contacts-view.tsx} | 424 ++- frontend/src/pages/dashboard.tsx | 451 ++- .../[lessonsId].tsx => deals/[dealsId].tsx} | 654 +++-- .../lessons-edit.tsx => deals/deals-edit.tsx} | 646 +++-- frontend/src/pages/deals/deals-list.tsx | 201 ++ .../lessons-new.tsx => deals/deals-new.tsx} | 530 ++-- .../deals-table.tsx} | 50 +- .../courses-view.tsx => deals/deals-view.tsx} | 667 ++--- frontend/src/pages/index.tsx | 6 +- .../[leadsId].tsx} | 568 ++-- .../leads-edit.tsx} | 560 ++-- .../leads-list.tsx} | 44 +- .../leads-new.tsx} | 326 ++- .../leads-table.tsx} | 46 +- .../lessons-view.tsx => leads/leads-view.tsx} | 359 ++- frontend/src/pages/login.tsx | 14 +- .../pages/permissions/permissions-view.tsx | 1 + .../pipeline_stages/[pipeline_stagesId].tsx | 383 +++ .../pipeline_stages/pipeline_stages-edit.tsx | 380 +++ .../pipeline_stages-list.tsx} | 50 +- .../pipeline_stages/pipeline_stages-new.tsx | 297 ++ .../pipeline_stages/pipeline_stages-table.tsx | 164 ++ .../pipeline_stages/pipeline_stages-view.tsx | 327 +++ frontend/src/pages/privacy-policy.tsx | 2 +- frontend/src/pages/roles/[rolesId].tsx | 2 + frontend/src/pages/roles/roles-edit.tsx | 2 + frontend/src/pages/roles/roles-view.tsx | 3 + frontend/src/pages/terms-of-use.tsx | 2 +- frontend/src/pages/users/[usersId].tsx | 4 + frontend/src/pages/users/users-edit.tsx | 4 + frontend/src/pages/users/users-view.tsx | 357 ++- .../activitiesSlice.ts} | 50 +- .../contactsSlice.ts} | 50 +- .../lessonsSlice.ts => deals/dealsSlice.ts} | 50 +- .../coursesSlice.ts => leads/leadsSlice.ts} | 50 +- .../pipeline_stages/pipeline_stagesSlice.ts | 231 ++ frontend/src/stores/store.ts | 18 +- 117 files changed, 13787 insertions(+), 8017 deletions(-) rename backend/src/db/api/{progress.js => activities.js} (61%) create mode 100644 backend/src/db/api/contacts.js rename backend/src/db/api/{lessons.js => deals.js} (62%) rename backend/src/db/api/{enrollments.js => leads.js} (60%) rename backend/src/db/api/{courses.js => pipeline_stages.js} (50%) rename backend/src/db/migrations/{1768904423064.js => 1769169079630.js} (76%) rename backend/src/db/models/{enrollments.js => activities.js} (61%) create mode 100644 backend/src/db/models/contacts.js rename backend/src/db/models/{lessons.js => deals.js} (59%) rename backend/src/db/models/{courses.js => leads.js} (54%) rename backend/src/db/models/{progress.js => pipeline_stages.js} (66%) rename backend/src/routes/{lessons.js => activities.js} (76%) rename backend/src/routes/{progress.js => contacts.js} (75%) create mode 100644 backend/src/routes/deals.js rename backend/src/routes/{courses.js => leads.js} (77%) rename backend/src/routes/{enrollments.js => pipeline_stages.js} (75%) create mode 100644 backend/src/routes/sql.js rename backend/src/services/{enrollments.js => activities.js} (84%) rename backend/src/services/{progress.js => contacts.js} (85%) rename backend/src/services/{courses.js => deals.js} (85%) rename backend/src/services/{lessons.js => leads.js} (85%) create mode 100644 backend/src/services/pipeline_stages.js rename frontend/src/components/{Courses/CardCourses.tsx => Activities/CardActivities.tsx} (70%) rename frontend/src/components/{Lessons/ListLessons.tsx => Activities/ListActivities.tsx} (73%) rename frontend/src/components/{Courses/TableCourses.tsx => Activities/TableActivities.tsx} (94%) rename frontend/src/components/{Lessons/configureLessonsCols.tsx => Activities/configureActivitiesCols.tsx} (70%) rename frontend/src/components/{Progress/CardProgress.tsx => Contacts/CardContacts.tsx} (84%) rename frontend/src/components/{Progress/ListProgress.tsx => Contacts/ListContacts.tsx} (84%) rename frontend/src/components/{Progress/TableProgress.tsx => Contacts/TableContacts.tsx} (96%) rename frontend/src/components/{Progress/configureProgressCols.tsx => Contacts/configureContactsCols.tsx} (75%) rename frontend/src/components/{Lessons/CardLessons.tsx => Deals/CardDeals.tsx} (77%) rename frontend/src/components/{Courses/ListCourses.tsx => Deals/ListDeals.tsx} (75%) rename frontend/src/components/{Lessons/TableLessons.tsx => Deals/TableDeals.tsx} (95%) rename frontend/src/components/{Courses/configureCoursesCols.tsx => Deals/configureDealsCols.tsx} (78%) rename frontend/src/components/{Enrollments/CardEnrollments.tsx => Leads/CardLeads.tsx} (76%) rename frontend/src/components/{Enrollments/ListEnrollments.tsx => Leads/ListLeads.tsx} (76%) rename frontend/src/components/{Enrollments/TableEnrollments.tsx => Leads/TableLeads.tsx} (96%) rename frontend/src/components/{Enrollments/configureEnrollmentsCols.tsx => Leads/configureLeadsCols.tsx} (76%) create mode 100644 frontend/src/components/Pipeline_stages/CardPipeline_stages.tsx create mode 100644 frontend/src/components/Pipeline_stages/ListPipeline_stages.tsx create mode 100644 frontend/src/components/Pipeline_stages/TablePipeline_stages.tsx create mode 100644 frontend/src/components/Pipeline_stages/configurePipeline_stagesCols.tsx rename frontend/src/pages/{courses/[coursesId].tsx => activities/[activitiesId].tsx} (60%) rename frontend/src/pages/{courses/courses-edit.tsx => activities/activities-edit.tsx} (61%) rename frontend/src/pages/{courses/courses-list.tsx => activities/activities-list.tsx} (74%) rename frontend/src/pages/{courses/courses-new.tsx => activities/activities-new.tsx} (58%) rename frontend/src/pages/{courses/courses-table.tsx => activities/activities-table.tsx} (75%) rename frontend/src/pages/{progress/progress-view.tsx => activities/activities-view.tsx} (60%) rename frontend/src/pages/{progress/[progressId].tsx => contacts/[contactsId].tsx} (63%) rename frontend/src/pages/{progress/progress-edit.tsx => contacts/contacts-edit.tsx} (65%) rename frontend/src/pages/{progress/progress-list.tsx => contacts/contacts-list.tsx} (79%) rename frontend/src/pages/{progress/progress-new.tsx => contacts/contacts-new.tsx} (68%) rename frontend/src/pages/{progress/progress-table.tsx => contacts/contacts-table.tsx} (79%) rename frontend/src/pages/{enrollments/enrollments-view.tsx => contacts/contacts-view.tsx} (66%) rename frontend/src/pages/{lessons/[lessonsId].tsx => deals/[dealsId].tsx} (64%) rename frontend/src/pages/{lessons/lessons-edit.tsx => deals/deals-edit.tsx} (66%) create mode 100644 frontend/src/pages/deals/deals-list.tsx rename frontend/src/pages/{lessons/lessons-new.tsx => deals/deals-new.tsx} (67%) rename frontend/src/pages/{lessons/lessons-table.tsx => deals/deals-table.tsx} (78%) rename frontend/src/pages/{courses/courses-view.tsx => deals/deals-view.tsx} (52%) rename frontend/src/pages/{enrollments/[enrollmentsId].tsx => leads/[leadsId].tsx} (63%) rename frontend/src/pages/{enrollments/enrollments-edit.tsx => leads/leads-edit.tsx} (65%) rename frontend/src/pages/{enrollments/enrollments-list.tsx => leads/leads-list.tsx} (75%) rename frontend/src/pages/{enrollments/enrollments-new.tsx => leads/leads-new.tsx} (62%) rename frontend/src/pages/{enrollments/enrollments-table.tsx => leads/leads-table.tsx} (75%) rename frontend/src/pages/{lessons/lessons-view.tsx => leads/leads-view.tsx} (66%) create mode 100644 frontend/src/pages/pipeline_stages/[pipeline_stagesId].tsx create mode 100644 frontend/src/pages/pipeline_stages/pipeline_stages-edit.tsx rename frontend/src/pages/{lessons/lessons-list.tsx => pipeline_stages/pipeline_stages-list.tsx} (75%) create mode 100644 frontend/src/pages/pipeline_stages/pipeline_stages-new.tsx create mode 100644 frontend/src/pages/pipeline_stages/pipeline_stages-table.tsx create mode 100644 frontend/src/pages/pipeline_stages/pipeline_stages-view.tsx rename frontend/src/stores/{enrollments/enrollmentsSlice.ts => activities/activitiesSlice.ts} (77%) rename frontend/src/stores/{progress/progressSlice.ts => contacts/contactsSlice.ts} (81%) rename frontend/src/stores/{lessons/lessonsSlice.ts => deals/dealsSlice.ts} (79%) rename frontend/src/stores/{courses/coursesSlice.ts => leads/leadsSlice.ts} (79%) create mode 100644 frontend/src/stores/pipeline_stages/pipeline_stagesSlice.ts diff --git a/.gitignore b/.gitignore index e427ff3..35390a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +/backend/node_modules +/frontend/node_modules node_modules/ */node_modules/ -*/build/ +**/node_modules/ +*/build/ \ No newline at end of file diff --git a/502.html b/502.html index 250d795..61809d2 100644 --- a/502.html +++ b/502.html @@ -129,8 +129,8 @@

The application is currently launching. The page will automatically refresh once site is available.

-

Instructor-Student LMS

-

Instructor-student LMS for courses, lessons, enrollments, and progress tracking.

+

Sales Pipeline CRM

+

Sales Pipeline CRM for managing leads, deals, contacts, activities and automated follow-ups.

App Logo CREATE DATABASE db_instructor_student_lms;` + - `postgres=> CREATE DATABASE db_sales_pipeline_crm;` - Then give that new user privileges to the new database then quit the `psql`. - - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_instructor_student_lms TO admin;` + - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_sales_pipeline_crm TO admin;` - `postgres=> \q` ------------ diff --git a/backend/package.json b/backend/package.json index 029380f..8d6ab72 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { - "name": "instructorstudentlms", - "description": "Instructor-Student LMS - template backend", + "name": "salespipelinecrm", + "description": "Sales Pipeline CRM - template backend", "scripts": { "start": "npm run db:migrate && npm run db:seed && npm run watch", "lint": "eslint . --ext .js", diff --git a/backend/src/config.js b/backend/src/config.js index bb7152b..964b760 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -11,15 +11,15 @@ const config = { bcrypt: { saltRounds: 12 }, - admin_pass: "e52941c5", - user_pass: "20ceb5eda155", + admin_pass: "72a5084e", + user_pass: "c6ada26bc4fa", admin_email: "admin@flatlogic.com", providers: { LOCAL: 'local', GOOGLE: 'google', MICROSOFT: 'microsoft' }, - secret_key: process.env.SECRET_KEY || 'e52941c5-df65-471d-838b-20ceb5eda155', + secret_key: process.env.SECRET_KEY || '72a5084e-302a-4f10-bdae-c6ada26bc4fa', remote: '', port: process.env.NODE_ENV === "production" ? "" : "8080", hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost", @@ -39,7 +39,7 @@ const config = { }, uploadDir: os.tmpdir(), email: { - from: 'Instructor-Student LMS ', + from: 'Sales Pipeline CRM ', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, auth: { @@ -56,11 +56,11 @@ const config = { - user: 'Student', + user: 'Support Specialist', }, - project_uuid: 'e52941c5-df65-471d-838b-20ceb5eda155', + project_uuid: '72a5084e-302a-4f10-bdae-c6ada26bc4fa', flHost: process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'dev_stage' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects', @@ -69,7 +69,7 @@ const config = { config.pexelsKey = process.env.PEXELS_KEY || ''; -config.pexelsQuery = 'soaring paper airplane over calm sea'; +config.pexelsQuery = 'mountain path leading to sunrise'; config.host = process.env.NODE_ENV === "production" ? config.remote : "http://localhost"; config.apiUrl = `${config.host}${config.port ? `:${config.port}` : ``}/api`; config.swaggerUrl = `${config.swaggerUI}${config.swaggerPort}`; diff --git a/backend/src/db/api/progress.js b/backend/src/db/api/activities.js similarity index 61% rename from backend/src/db/api/progress.js rename to backend/src/db/api/activities.js index 306ed96..741f349 100644 --- a/backend/src/db/api/progress.js +++ b/backend/src/db/api/activities.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class ProgressDBApi { +module.exports = class ActivitiesDBApi { @@ -17,11 +17,26 @@ module.exports = class ProgressDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const progress = await db.progress.create( + const activities = await db.activities.create( { id: data.id || undefined, - note: data.note + subject: data.subject + || + null + , + + activity_type: data.activity_type + || + null + , + + start: data.start + || + null + , + + end: data.end || null , @@ -32,17 +47,7 @@ module.exports = class ProgressDBApi { , - completed_at: data.completed_at - || - null - , - - score: data.score - || - null - , - - attempts: data.attempts + notes: data.notes || null , @@ -55,11 +60,15 @@ module.exports = class ProgressDBApi { ); - await progress.setEnrollment( data.enrollment || null, { + await activities.setOwner( data.owner || null, { transaction, }); - await progress.setLesson( data.lesson || null, { + await activities.setRelated_deal( data.related_deal || null, { + transaction, + }); + + await activities.setRelated_lead( data.related_lead || null, { transaction, }); @@ -68,7 +77,7 @@ module.exports = class ProgressDBApi { - return progress; + return activities; } @@ -77,10 +86,25 @@ module.exports = class ProgressDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const progressData = data.map((item, index) => ({ + const activitiesData = data.map((item, index) => ({ id: item.id || undefined, - note: item.note + subject: item.subject + || + null + , + + activity_type: item.activity_type + || + null + , + + start: item.start + || + null + , + + end: item.end || null , @@ -91,17 +115,7 @@ module.exports = class ProgressDBApi { , - completed_at: item.completed_at - || - null - , - - score: item.score - || - null - , - - attempts: item.attempts + notes: item.notes || null , @@ -113,12 +127,12 @@ module.exports = class ProgressDBApi { })); // Bulk create items - const progress = await db.progress.bulkCreate(progressData, { transaction }); + const activities = await db.activities.bulkCreate(activitiesData, { transaction }); // For each item created, replace relation files - return progress; + return activities; } static async update(id, data, options) { @@ -126,47 +140,59 @@ module.exports = class ProgressDBApi { const transaction = (options && options.transaction) || undefined; - const progress = await db.progress.findByPk(id, {}, {transaction}); + const activities = await db.activities.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.note !== undefined) updatePayload.note = data.note; + if (data.subject !== undefined) updatePayload.subject = data.subject; + + + if (data.activity_type !== undefined) updatePayload.activity_type = data.activity_type; + + + if (data.start !== undefined) updatePayload.start = data.start; + + + if (data.end !== undefined) updatePayload.end = data.end; if (data.completed !== undefined) updatePayload.completed = data.completed; - if (data.completed_at !== undefined) updatePayload.completed_at = data.completed_at; - - - if (data.score !== undefined) updatePayload.score = data.score; - - - if (data.attempts !== undefined) updatePayload.attempts = data.attempts; + if (data.notes !== undefined) updatePayload.notes = data.notes; updatePayload.updatedById = currentUser.id; - await progress.update(updatePayload, {transaction}); + await activities.update(updatePayload, {transaction}); - if (data.enrollment !== undefined) { - await progress.setEnrollment( + if (data.owner !== undefined) { + await activities.setOwner( - data.enrollment, + data.owner, { transaction } ); } - if (data.lesson !== undefined) { - await progress.setLesson( + if (data.related_deal !== undefined) { + await activities.setRelated_deal( - data.lesson, + data.related_deal, + + { transaction } + ); + } + + if (data.related_lead !== undefined) { + await activities.setRelated_lead( + + data.related_lead, { transaction } ); @@ -178,14 +204,14 @@ module.exports = class ProgressDBApi { - return progress; + return activities; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const progress = await db.progress.findAll({ + const activities = await db.activities.findAll({ where: { id: { [Op.in]: ids, @@ -195,53 +221,53 @@ module.exports = class ProgressDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of progress) { + for (const record of activities) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of progress) { + for (const record of activities) { await record.destroy({transaction}); } }); - return progress; + return activities; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const progress = await db.progress.findByPk(id, options); + const activities = await db.activities.findByPk(id, options); - await progress.update({ + await activities.update({ deletedBy: currentUser.id }, { transaction, }); - await progress.destroy({ + await activities.destroy({ transaction }); - return progress; + return activities; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const progress = await db.progress.findOne( + const activities = await db.activities.findOne( { where }, { transaction }, ); - if (!progress) { - return progress; + if (!activities) { + return activities; } - const output = progress.get({plain: true}); + const output = activities.get({plain: true}); @@ -252,12 +278,18 @@ module.exports = class ProgressDBApi { - output.enrollment = await progress.getEnrollment({ + + output.owner = await activities.getOwner({ transaction }); - output.lesson = await progress.getLesson({ + output.related_deal = await activities.getRelated_deal({ + transaction + }); + + + output.related_lead = await activities.getRelated_lead({ transaction }); @@ -288,15 +320,15 @@ module.exports = class ProgressDBApi { let include = [ { - model: db.enrollments, - as: 'enrollment', + model: db.users, + as: 'owner', - where: filter.enrollment ? { + where: filter.owner ? { [Op.or]: [ - { id: { [Op.in]: filter.enrollment.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } }, { - enrollment_label: { - [Op.or]: filter.enrollment.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + firstName: { + [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -305,15 +337,32 @@ module.exports = class ProgressDBApi { }, { - model: db.lessons, - as: 'lesson', + model: db.deals, + as: 'related_deal', - where: filter.lesson ? { + where: filter.related_deal ? { [Op.or]: [ - { id: { [Op.in]: filter.lesson.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.related_deal.split('|').map(term => Utils.uuid(term)) } }, { title: { - [Op.or]: filter.lesson.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + [Op.or]: filter.related_deal.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + } + }, + ] + } : {}, + + }, + + { + model: db.leads, + as: 'related_lead', + + where: filter.related_lead ? { + [Op.or]: [ + { id: { [Op.in]: filter.related_lead.split('|').map(term => Utils.uuid(term)) } }, + { + name: { + [Op.or]: filter.related_lead.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -334,13 +383,24 @@ module.exports = class ProgressDBApi { } - if (filter.note) { + if (filter.subject) { where = { ...where, [Op.and]: Utils.ilike( - 'progress', - 'note', - filter.note, + 'activities', + 'subject', + filter.subject, + ), + }; + } + + if (filter.notes) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'activities', + 'notes', + filter.notes, ), }; } @@ -348,16 +408,34 @@ module.exports = class ProgressDBApi { + if (filter.calendarStart && filter.calendarEnd) { + where = { + ...where, + [Op.or]: [ + { + start: { + [Op.between]: [filter.calendarStart, filter.calendarEnd], + }, + }, + { + end: { + [Op.between]: [filter.calendarStart, filter.calendarEnd], + }, + }, + ], + }; + } + - if (filter.completed_atRange) { - const [start, end] = filter.completed_atRange; + if (filter.startRange) { + const [start, end] = filter.startRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - completed_at: { - ...where.completed_at, + start: { + ...where.start, [Op.gte]: start, }, }; @@ -366,22 +444,22 @@ module.exports = class ProgressDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - completed_at: { - ...where.completed_at, + start: { + ...where.start, [Op.lte]: end, }, }; } } - if (filter.scoreRange) { - const [start, end] = filter.scoreRange; + if (filter.endRange) { + const [start, end] = filter.endRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - score: { - ...where.score, + end: { + ...where.end, [Op.gte]: start, }, }; @@ -390,32 +468,8 @@ module.exports = class ProgressDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - score: { - ...where.score, - [Op.lte]: end, - }, - }; - } - } - - if (filter.attemptsRange) { - const [start, end] = filter.attemptsRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - attempts: { - ...where.attempts, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - attempts: { - ...where.attempts, + end: { + ...where.end, [Op.lte]: end, }, }; @@ -431,6 +485,13 @@ module.exports = class ProgressDBApi { } + if (filter.activity_type) { + where = { + ...where, + activity_type: filter.activity_type, + }; + } + if (filter.completed) { where = { ...where, @@ -444,6 +505,8 @@ module.exports = class ProgressDBApi { + + if (filter.createdAtRange) { @@ -491,7 +554,7 @@ module.exports = class ProgressDBApi { } try { - const { rows, count } = await db.progress.findAndCountAll(queryOptions); + const { rows, count } = await db.activities.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -513,25 +576,25 @@ module.exports = class ProgressDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'progress', - 'note', + 'activities', + 'subject', query, ), ], }; } - const records = await db.progress.findAll({ - attributes: [ 'id', 'note' ], + const records = await db.activities.findAll({ + attributes: [ 'id', 'subject' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['note', 'ASC']], + orderBy: [['subject', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.note, + label: record.subject, })); } diff --git a/backend/src/db/api/contacts.js b/backend/src/db/api/contacts.js new file mode 100644 index 0000000..5a478ba --- /dev/null +++ b/backend/src/db/api/contacts.js @@ -0,0 +1,495 @@ + +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, + + name: data.name + || + null + , + + email: data.email + || + null + , + + phone: data.phone + || + null + , + + title: data.title + || + null + , + + company: data.company + || + null + , + + notes: data.notes + || + null + , + + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { 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, + + name: item.name + || + null + , + + email: item.email + || + null + , + + phone: item.phone + || + null + , + + title: item.title + || + null + , + + company: item.company + || + 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 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.name !== undefined) updatePayload.name = data.name; + + + if (data.email !== undefined) updatePayload.email = data.email; + + + if (data.phone !== undefined) updatePayload.phone = data.phone; + + + if (data.title !== undefined) updatePayload.title = data.title; + + + if (data.company !== undefined) updatePayload.company = data.company; + + + if (data.notes !== undefined) updatePayload.notes = data.notes; + + + updatePayload.updatedById = currentUser.id; + + await contacts.update(updatePayload, {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.deals_primary_contact = await contacts.getDeals_primary_contact({ + 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.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( + 'contacts', + 'name', + filter.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.title) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'contacts', + 'title', + filter.title, + ), + }; + } + + if (filter.company) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'contacts', + 'company', + filter.company, + ), + }; + } + + if (filter.notes) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'contacts', + 'notes', + filter.notes, + ), + }; + } + + + + + + + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true' + }; + } + + + + + + + + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.lte]: end, + }, + }; + } + } + } + + + + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options?.transaction, + logging: console.log + }; + + if (!options?.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await db.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', + 'name', + query, + ), + ], + }; + } + + const records = await db.contacts.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/lessons.js b/backend/src/db/api/deals.js similarity index 62% rename from backend/src/db/api/lessons.js rename to backend/src/db/api/deals.js index b86dbf6..10d3156 100644 --- a/backend/src/db/api/lessons.js +++ b/backend/src/db/api/deals.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class LessonsDBApi { +module.exports = class DealsDBApi { @@ -17,7 +17,7 @@ module.exports = class LessonsDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const lessons = await db.lessons.create( + const deals = await db.deals.create( { id: data.id || undefined, @@ -26,28 +26,33 @@ module.exports = class LessonsDBApi { null , - content: data.content + deal_number: data.deal_number || null , - order: data.order + value: data.value || null , - duration_minutes: data.duration_minutes - || - null - , - - release_date: data.release_date + currency: data.currency || null , status: data.status || + null + , + + close_date: data.close_date + || + null + , + + description: data.description + || null , @@ -59,7 +64,15 @@ module.exports = class LessonsDBApi { ); - await lessons.setCourse( data.course || null, { + await deals.setStage( data.stage || null, { + transaction, + }); + + await deals.setOwner( data.owner || null, { + transaction, + }); + + await deals.setPrimary_contact( data.primary_contact || null, { transaction, }); @@ -67,28 +80,8 @@ module.exports = class LessonsDBApi { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'video_files', - belongsToId: lessons.id, - }, - data.video_files, - options, - ); - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'resources', - belongsToId: lessons.id, - }, - data.resources, - options, - ); - - return lessons; + return deals; } @@ -97,7 +90,7 @@ module.exports = class LessonsDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const lessonsData = data.map((item, index) => ({ + const dealsData = data.map((item, index) => ({ id: item.id || undefined, title: item.title @@ -105,22 +98,17 @@ module.exports = class LessonsDBApi { null , - content: item.content + deal_number: item.deal_number || null , - order: item.order + value: item.value || null , - duration_minutes: item.duration_minutes - || - null - , - - release_date: item.release_date + currency: item.currency || null , @@ -128,6 +116,16 @@ module.exports = class LessonsDBApi { status: item.status || null + , + + close_date: item.close_date + || + null + , + + description: item.description + || + null , importHash: item.importHash || null, @@ -137,36 +135,12 @@ module.exports = class LessonsDBApi { })); // Bulk create items - const lessons = await db.lessons.bulkCreate(lessonsData, { transaction }); + const deals = await db.deals.bulkCreate(dealsData, { transaction }); // For each item created, replace relation files - for (let i = 0; i < lessons.length; i++) { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'video_files', - belongsToId: lessons[i].id, - }, - data[i].video_files, - options, - ); - } - - for (let i = 0; i < lessons.length; i++) { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'resources', - belongsToId: lessons[i].id, - }, - data[i].resources, - options, - ); - } - - return lessons; + return deals; } static async update(id, data, options) { @@ -174,7 +148,7 @@ module.exports = class LessonsDBApi { const transaction = (options && options.transaction) || undefined; - const lessons = await db.lessons.findByPk(id, {}, {transaction}); + const deals = await db.deals.findByPk(id, {}, {transaction}); @@ -184,31 +158,52 @@ module.exports = class LessonsDBApi { if (data.title !== undefined) updatePayload.title = data.title; - if (data.content !== undefined) updatePayload.content = data.content; + if (data.deal_number !== undefined) updatePayload.deal_number = data.deal_number; - if (data.order !== undefined) updatePayload.order = data.order; + if (data.value !== undefined) updatePayload.value = data.value; - if (data.duration_minutes !== undefined) updatePayload.duration_minutes = data.duration_minutes; - - - if (data.release_date !== undefined) updatePayload.release_date = data.release_date; + if (data.currency !== undefined) updatePayload.currency = data.currency; if (data.status !== undefined) updatePayload.status = data.status; + if (data.close_date !== undefined) updatePayload.close_date = data.close_date; + + + if (data.description !== undefined) updatePayload.description = data.description; + + updatePayload.updatedById = currentUser.id; - await lessons.update(updatePayload, {transaction}); + await deals.update(updatePayload, {transaction}); - if (data.course !== undefined) { - await lessons.setCourse( + if (data.stage !== undefined) { + await deals.setStage( - data.course, + data.stage, + + { transaction } + ); + } + + if (data.owner !== undefined) { + await deals.setOwner( + + data.owner, + + { transaction } + ); + } + + if (data.primary_contact !== undefined) { + await deals.setPrimary_contact( + + data.primary_contact, { transaction } ); @@ -219,35 +214,15 @@ module.exports = class LessonsDBApi { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'video_files', - belongsToId: lessons.id, - }, - data.video_files, - options, - ); - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'resources', - belongsToId: lessons.id, - }, - data.resources, - options, - ); - - return lessons; + return deals; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const lessons = await db.lessons.findAll({ + const deals = await db.deals.findAll({ where: { id: { [Op.in]: ids, @@ -257,53 +232,53 @@ module.exports = class LessonsDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of lessons) { + for (const record of deals) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of lessons) { + for (const record of deals) { await record.destroy({transaction}); } }); - return lessons; + return deals; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const lessons = await db.lessons.findByPk(id, options); + const deals = await db.deals.findByPk(id, options); - await lessons.update({ + await deals.update({ deletedBy: currentUser.id }, { transaction, }); - await lessons.destroy({ + await deals.destroy({ transaction }); - return lessons; + return deals; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const lessons = await db.lessons.findOne( + const deals = await db.deals.findOne( { where }, { transaction }, ); - if (!lessons) { - return lessons; + if (!deals) { + return deals; } - const output = lessons.get({plain: true}); + const output = deals.get({plain: true}); @@ -312,23 +287,24 @@ module.exports = class LessonsDBApi { - output.progress_lesson = await lessons.getProgress_lesson({ + + output.activities_related_deal = await deals.getActivities_related_deal({ transaction }); - output.course = await lessons.getCourse({ + output.stage = await deals.getStage({ transaction }); - output.video_files = await lessons.getVideo_files({ + output.owner = await deals.getOwner({ transaction }); - output.resources = await lessons.getResources({ + output.primary_contact = await deals.getPrimary_contact({ transaction }); @@ -359,15 +335,49 @@ module.exports = class LessonsDBApi { let include = [ { - model: db.courses, - as: 'course', + model: db.pipeline_stages, + as: 'stage', - where: filter.course ? { + where: filter.stage ? { [Op.or]: [ - { id: { [Op.in]: filter.course.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.stage.split('|').map(term => Utils.uuid(term)) } }, { title: { - [Op.or]: filter.course.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + [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.contacts, + as: 'primary_contact', + + where: filter.primary_contact ? { + [Op.or]: [ + { id: { [Op.in]: filter.primary_contact.split('|').map(term => Utils.uuid(term)) } }, + { + name: { + [Op.or]: filter.primary_contact.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -377,16 +387,6 @@ module.exports = class LessonsDBApi { - { - model: db.file, - as: 'video_files', - }, - - { - model: db.file, - as: 'resources', - }, - ]; if (filter) { @@ -402,20 +402,42 @@ module.exports = class LessonsDBApi { where = { ...where, [Op.and]: Utils.ilike( - 'lessons', + 'deals', 'title', filter.title, ), }; } - if (filter.content) { + if (filter.deal_number) { where = { ...where, [Op.and]: Utils.ilike( - 'lessons', - 'content', - filter.content, + 'deals', + 'deal_number', + filter.deal_number, + ), + }; + } + + if (filter.currency) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'deals', + 'currency', + filter.currency, + ), + }; + } + + if (filter.description) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'deals', + 'description', + filter.description, ), }; } @@ -425,14 +447,14 @@ module.exports = class LessonsDBApi { - if (filter.orderRange) { - const [start, end] = filter.orderRange; + if (filter.valueRange) { + const [start, end] = filter.valueRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - order: { - ...where.order, + value: { + ...where.value, [Op.gte]: start, }, }; @@ -441,22 +463,22 @@ module.exports = class LessonsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - order: { - ...where.order, + value: { + ...where.value, [Op.lte]: end, }, }; } } - if (filter.duration_minutesRange) { - const [start, end] = filter.duration_minutesRange; + if (filter.close_dateRange) { + const [start, end] = filter.close_dateRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - duration_minutes: { - ...where.duration_minutes, + close_date: { + ...where.close_date, [Op.gte]: start, }, }; @@ -465,32 +487,8 @@ module.exports = class LessonsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - duration_minutes: { - ...where.duration_minutes, - [Op.lte]: end, - }, - }; - } - } - - if (filter.release_dateRange) { - const [start, end] = filter.release_dateRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - release_date: { - ...where.release_date, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - release_date: { - ...where.release_date, + close_date: { + ...where.close_date, [Op.lte]: end, }, }; @@ -517,6 +515,10 @@ module.exports = class LessonsDBApi { + + + + if (filter.createdAtRange) { @@ -564,7 +566,7 @@ module.exports = class LessonsDBApi { } try { - const { rows, count } = await db.lessons.findAndCountAll(queryOptions); + const { rows, count } = await db.deals.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -576,6 +578,21 @@ module.exports = class LessonsDBApi { } } + static async stats(options) { + const transaction = (options && options.transaction) || undefined; + const currentUser = (options && options.currentUser) || { id: null }; + + const result = await db.deals.findOne({ + attributes: [ + [db.Sequelize.fn('SUM', db.Sequelize.col('value')), 'totalValue'], + [db.Sequelize.fn('COUNT', db.Sequelize.col('id')), 'count'] + ], + transaction, + }); + + return result.get({ plain: true }); + } + static async findAllAutocomplete(query, limit, offset, ) { let where = {}; @@ -586,7 +603,7 @@ module.exports = class LessonsDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'lessons', + 'deals', 'title', query, ), @@ -594,7 +611,7 @@ module.exports = class LessonsDBApi { }; } - const records = await db.lessons.findAll({ + const records = await db.deals.findAll({ attributes: [ 'id', 'title' ], where, limit: limit ? Number(limit) : undefined, diff --git a/backend/src/db/api/enrollments.js b/backend/src/db/api/leads.js similarity index 60% rename from backend/src/db/api/enrollments.js rename to backend/src/db/api/leads.js index 6ec14e3..14839fc 100644 --- a/backend/src/db/api/enrollments.js +++ b/backend/src/db/api/leads.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class EnrollmentsDBApi { +module.exports = class LeadsDBApi { @@ -17,16 +17,31 @@ module.exports = class EnrollmentsDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const enrollments = await db.enrollments.create( + const leads = await db.leads.create( { id: data.id || undefined, - enrollment_label: data.enrollment_label + name: data.name || null , - enrolled_at: data.enrolled_at + company: data.company + || + null + , + + email: data.email + || + null + , + + phone: data.phone + || + null + , + + source: data.source || null , @@ -36,12 +51,12 @@ module.exports = class EnrollmentsDBApi { null , - progress_percent: data.progress_percent + estimated_value: data.estimated_value || null , - price_paid: data.price_paid + notes: data.notes || null , @@ -54,11 +69,7 @@ module.exports = class EnrollmentsDBApi { ); - await enrollments.setStudent( data.student || null, { - transaction, - }); - - await enrollments.setCourse( data.course || null, { + await leads.setOwner( data.owner || null, { transaction, }); @@ -67,7 +78,7 @@ module.exports = class EnrollmentsDBApi { - return enrollments; + return leads; } @@ -76,15 +87,30 @@ module.exports = class EnrollmentsDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const enrollmentsData = data.map((item, index) => ({ + const leadsData = data.map((item, index) => ({ id: item.id || undefined, - enrollment_label: item.enrollment_label + name: item.name || null , - enrolled_at: item.enrolled_at + company: item.company + || + null + , + + email: item.email + || + null + , + + phone: item.phone + || + null + , + + source: item.source || null , @@ -94,12 +120,12 @@ module.exports = class EnrollmentsDBApi { null , - progress_percent: item.progress_percent + estimated_value: item.estimated_value || null , - price_paid: item.price_paid + notes: item.notes || null , @@ -111,12 +137,12 @@ module.exports = class EnrollmentsDBApi { })); // Bulk create items - const enrollments = await db.enrollments.bulkCreate(enrollmentsData, { transaction }); + const leads = await db.leads.bulkCreate(leadsData, { transaction }); // For each item created, replace relation files - return enrollments; + return leads; } static async update(id, data, options) { @@ -124,47 +150,47 @@ module.exports = class EnrollmentsDBApi { const transaction = (options && options.transaction) || undefined; - const enrollments = await db.enrollments.findByPk(id, {}, {transaction}); + const leads = await db.leads.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.enrollment_label !== undefined) updatePayload.enrollment_label = data.enrollment_label; + if (data.name !== undefined) updatePayload.name = data.name; - if (data.enrolled_at !== undefined) updatePayload.enrolled_at = data.enrolled_at; + if (data.company !== undefined) updatePayload.company = data.company; + + + if (data.email !== undefined) updatePayload.email = data.email; + + + if (data.phone !== undefined) updatePayload.phone = data.phone; + + + if (data.source !== undefined) updatePayload.source = data.source; if (data.status !== undefined) updatePayload.status = data.status; - if (data.progress_percent !== undefined) updatePayload.progress_percent = data.progress_percent; + if (data.estimated_value !== undefined) updatePayload.estimated_value = data.estimated_value; - if (data.price_paid !== undefined) updatePayload.price_paid = data.price_paid; + if (data.notes !== undefined) updatePayload.notes = data.notes; updatePayload.updatedById = currentUser.id; - await enrollments.update(updatePayload, {transaction}); + await leads.update(updatePayload, {transaction}); - if (data.student !== undefined) { - await enrollments.setStudent( + if (data.owner !== undefined) { + await leads.setOwner( - data.student, - - { transaction } - ); - } - - if (data.course !== undefined) { - await enrollments.setCourse( - - data.course, + data.owner, { transaction } ); @@ -176,14 +202,14 @@ module.exports = class EnrollmentsDBApi { - return enrollments; + return leads; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const enrollments = await db.enrollments.findAll({ + const leads = await db.leads.findAll({ where: { id: { [Op.in]: ids, @@ -193,53 +219,53 @@ module.exports = class EnrollmentsDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of enrollments) { + for (const record of leads) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of enrollments) { + for (const record of leads) { await record.destroy({transaction}); } }); - return enrollments; + return leads; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const enrollments = await db.enrollments.findByPk(id, options); + const leads = await db.leads.findByPk(id, options); - await enrollments.update({ + await leads.update({ deletedBy: currentUser.id }, { transaction, }); - await enrollments.destroy({ + await leads.destroy({ transaction }); - return enrollments; + return leads; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const enrollments = await db.enrollments.findOne( + const leads = await db.leads.findOne( { where }, { transaction }, ); - if (!enrollments) { - return enrollments; + if (!leads) { + return leads; } - const output = enrollments.get({plain: true}); + const output = leads.get({plain: true}); @@ -248,18 +274,14 @@ module.exports = class EnrollmentsDBApi { - output.progress_enrollment = await enrollments.getProgress_enrollment({ + + output.activities_related_lead = await leads.getActivities_related_lead({ transaction }); - output.student = await enrollments.getStudent({ - transaction - }); - - - output.course = await enrollments.getCourse({ + output.owner = await leads.getOwner({ transaction }); @@ -291,31 +313,14 @@ module.exports = class EnrollmentsDBApi { { model: db.users, - as: 'student', + as: 'owner', - where: filter.student ? { + where: filter.owner ? { [Op.or]: [ - { id: { [Op.in]: filter.student.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.owner.split('|').map(term => Utils.uuid(term)) } }, { firstName: { - [Op.or]: filter.student.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.courses, - as: 'course', - - where: filter.course ? { - [Op.or]: [ - { id: { [Op.in]: filter.course.split('|').map(term => Utils.uuid(term)) } }, - { - title: { - [Op.or]: filter.course.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + [Op.or]: filter.owner.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -336,13 +341,57 @@ module.exports = class EnrollmentsDBApi { } - if (filter.enrollment_label) { + if (filter.name) { where = { ...where, [Op.and]: Utils.ilike( - 'enrollments', - 'enrollment_label', - filter.enrollment_label, + 'leads', + 'name', + filter.name, + ), + }; + } + + if (filter.company) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'leads', + 'company', + filter.company, + ), + }; + } + + 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, ), }; } @@ -352,14 +401,14 @@ module.exports = class EnrollmentsDBApi { - if (filter.enrolled_atRange) { - const [start, end] = filter.enrolled_atRange; + if (filter.estimated_valueRange) { + const [start, end] = filter.estimated_valueRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - enrolled_at: { - ...where.enrolled_at, + estimated_value: { + ...where.estimated_value, [Op.gte]: start, }, }; @@ -368,56 +417,8 @@ module.exports = class EnrollmentsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - enrolled_at: { - ...where.enrolled_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.progress_percentRange) { - const [start, end] = filter.progress_percentRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - progress_percent: { - ...where.progress_percent, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - progress_percent: { - ...where.progress_percent, - [Op.lte]: end, - }, - }; - } - } - - if (filter.price_paidRange) { - const [start, end] = filter.price_paidRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - price_paid: { - ...where.price_paid, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - price_paid: { - ...where.price_paid, + estimated_value: { + ...where.estimated_value, [Op.lte]: end, }, }; @@ -433,6 +434,13 @@ module.exports = class EnrollmentsDBApi { } + if (filter.source) { + where = { + ...where, + source: filter.source, + }; + } + if (filter.status) { where = { ...where, @@ -444,8 +452,6 @@ module.exports = class EnrollmentsDBApi { - - if (filter.createdAtRange) { @@ -493,7 +499,7 @@ module.exports = class EnrollmentsDBApi { } try { - const { rows, count } = await db.enrollments.findAndCountAll(queryOptions); + const { rows, count } = await db.leads.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -515,25 +521,25 @@ module.exports = class EnrollmentsDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'enrollments', - 'enrollment_label', + 'leads', + 'name', query, ), ], }; } - const records = await db.enrollments.findAll({ - attributes: [ 'id', 'enrollment_label' ], + const records = await db.leads.findAll({ + attributes: [ 'id', 'name' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['enrollment_label', 'ASC']], + orderBy: [['name', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.enrollment_label, + label: record.name, })); } diff --git a/backend/src/db/api/permissions.js b/backend/src/db/api/permissions.js index daceb98..254a103 100644 --- a/backend/src/db/api/permissions.js +++ b/backend/src/db/api/permissions.js @@ -172,6 +172,7 @@ module.exports = class PermissionsDBApi { + return output; } diff --git a/backend/src/db/api/courses.js b/backend/src/db/api/pipeline_stages.js similarity index 50% rename from backend/src/db/api/courses.js rename to backend/src/db/api/pipeline_stages.js index 4970221..a56a4bb 100644 --- a/backend/src/db/api/courses.js +++ b/backend/src/db/api/pipeline_stages.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class CoursesDBApi { +module.exports = class Pipeline_stagesDBApi { @@ -17,7 +17,7 @@ module.exports = class CoursesDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const courses = await db.courses.create( + const pipeline_stages = await db.pipeline_stages.create( { id: data.id || undefined, @@ -26,47 +26,22 @@ module.exports = class CoursesDBApi { null , - description: data.description + order: data.order || null , - category: data.category + probability: data.probability || null , - level: data.level - || - null - , - - published: data.published + is_default: data.is_default || false , - start_date: data.start_date - || - null - , - - end_date: data.end_date - || - null - , - - price: data.price - || - null - , - - language: data.language - || - null - , - importHash: data.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -75,26 +50,12 @@ module.exports = class CoursesDBApi { ); - await courses.setInstructor( data.instructor || null, { - transaction, - }); - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.courses.getTableName(), - belongsToColumn: 'thumbnail', - belongsToId: courses.id, - }, - data.thumbnail, - options, - ); - - return courses; + return pipeline_stages; } @@ -103,7 +64,7 @@ module.exports = class CoursesDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const coursesData = data.map((item, index) => ({ + const pipeline_stagesData = data.map((item, index) => ({ id: item.id || undefined, title: item.title @@ -111,45 +72,20 @@ module.exports = class CoursesDBApi { null , - description: item.description + order: item.order || null , - category: item.category + probability: item.probability || null , - level: item.level - || - null - , - - published: item.published + is_default: item.is_default || false - , - - start_date: item.start_date - || - null - , - - end_date: item.end_date - || - null - , - - price: item.price - || - null - , - - language: item.language - || - null , importHash: item.importHash || null, @@ -159,24 +95,12 @@ module.exports = class CoursesDBApi { })); // Bulk create items - const courses = await db.courses.bulkCreate(coursesData, { transaction }); + const pipeline_stages = await db.pipeline_stages.bulkCreate(pipeline_stagesData, { transaction }); // For each item created, replace relation files - for (let i = 0; i < courses.length; i++) { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.courses.getTableName(), - belongsToColumn: 'thumbnail', - belongsToId: courses[i].id, - }, - data[i].thumbnail, - options, - ); - } - - return courses; + return pipeline_stages; } static async update(id, data, options) { @@ -184,7 +108,7 @@ module.exports = class CoursesDBApi { const transaction = (options && options.transaction) || undefined; - const courses = await db.courses.findByPk(id, {}, {transaction}); + const pipeline_stages = await db.pipeline_stages.findByPk(id, {}, {transaction}); @@ -194,69 +118,35 @@ module.exports = class CoursesDBApi { if (data.title !== undefined) updatePayload.title = data.title; - if (data.description !== undefined) updatePayload.description = data.description; + if (data.order !== undefined) updatePayload.order = data.order; - if (data.category !== undefined) updatePayload.category = data.category; + if (data.probability !== undefined) updatePayload.probability = data.probability; - if (data.level !== undefined) updatePayload.level = data.level; - - - if (data.published !== undefined) updatePayload.published = data.published; - - - if (data.start_date !== undefined) updatePayload.start_date = data.start_date; - - - if (data.end_date !== undefined) updatePayload.end_date = data.end_date; - - - if (data.price !== undefined) updatePayload.price = data.price; - - - if (data.language !== undefined) updatePayload.language = data.language; + if (data.is_default !== undefined) updatePayload.is_default = data.is_default; updatePayload.updatedById = currentUser.id; - await courses.update(updatePayload, {transaction}); + await pipeline_stages.update(updatePayload, {transaction}); - if (data.instructor !== undefined) { - await courses.setInstructor( - - data.instructor, - - { transaction } - ); - } - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.courses.getTableName(), - belongsToColumn: 'thumbnail', - belongsToId: courses.id, - }, - data.thumbnail, - options, - ); - - return courses; + return pipeline_stages; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const courses = await db.courses.findAll({ + const pipeline_stages = await db.pipeline_stages.findAll({ where: { id: { [Op.in]: ids, @@ -266,79 +156,66 @@ module.exports = class CoursesDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of courses) { + for (const record of pipeline_stages) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of courses) { + for (const record of pipeline_stages) { await record.destroy({transaction}); } }); - return courses; + return pipeline_stages; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const courses = await db.courses.findByPk(id, options); + const pipeline_stages = await db.pipeline_stages.findByPk(id, options); - await courses.update({ + await pipeline_stages.update({ deletedBy: currentUser.id }, { transaction, }); - await courses.destroy({ + await pipeline_stages.destroy({ transaction }); - return courses; + return pipeline_stages; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const courses = await db.courses.findOne( + const pipeline_stages = await db.pipeline_stages.findOne( { where }, { transaction }, ); - if (!courses) { - return courses; + if (!pipeline_stages) { + return pipeline_stages; } - const output = courses.get({plain: true}); + const output = pipeline_stages.get({plain: true}); - output.lessons_course = await courses.getLessons_course({ + + + output.deals_stage = await pipeline_stages.getDeals_stage({ transaction }); - output.enrollments_course = await courses.getEnrollments_course({ - transaction - }); - - - - - output.instructor = await courses.getInstructor({ - transaction - }); - - - output.thumbnail = await courses.getThumbnail({ - transaction - }); @@ -366,30 +243,8 @@ module.exports = class CoursesDBApi { let include = [ - { - model: db.users, - as: 'instructor', - - where: filter.instructor ? { - [Op.or]: [ - { id: { [Op.in]: filter.instructor.split('|').map(term => Utils.uuid(term)) } }, - { - firstName: { - [Op.or]: filter.instructor.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - - { - model: db.file, - as: 'thumbnail', - }, - ]; if (filter) { @@ -405,66 +260,26 @@ module.exports = class CoursesDBApi { where = { ...where, [Op.and]: Utils.ilike( - 'courses', + 'pipeline_stages', 'title', filter.title, ), }; } - if (filter.description) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'courses', - 'description', - filter.description, - ), - }; - } - - if (filter.language) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'courses', - 'language', - filter.language, - ), - }; - } - - if (filter.calendarStart && filter.calendarEnd) { - where = { - ...where, - [Op.or]: [ - { - start_date: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - { - end_date: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - ], - }; - } - - if (filter.start_dateRange) { - const [start, end] = filter.start_dateRange; + if (filter.orderRange) { + const [start, end] = filter.orderRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - start_date: { - ...where.start_date, + order: { + ...where.order, [Op.gte]: start, }, }; @@ -473,22 +288,22 @@ module.exports = class CoursesDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - start_date: { - ...where.start_date, + order: { + ...where.order, [Op.lte]: end, }, }; } } - if (filter.end_dateRange) { - const [start, end] = filter.end_dateRange; + if (filter.probabilityRange) { + const [start, end] = filter.probabilityRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - end_date: { - ...where.end_date, + probability: { + ...where.probability, [Op.gte]: start, }, }; @@ -497,32 +312,8 @@ module.exports = class CoursesDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - end_date: { - ...where.end_date, - [Op.lte]: end, - }, - }; - } - } - - if (filter.priceRange) { - const [start, end] = filter.priceRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - price: { - ...where.price, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - price: { - ...where.price, + probability: { + ...where.probability, [Op.lte]: end, }, }; @@ -538,31 +329,15 @@ module.exports = class CoursesDBApi { } - if (filter.category) { + if (filter.is_default) { where = { ...where, - category: filter.category, - }; - } - - if (filter.level) { - where = { - ...where, - level: filter.level, - }; - } - - if (filter.published) { - where = { - ...where, - published: filter.published, + is_default: filter.is_default, }; } - - if (filter.createdAtRange) { @@ -610,7 +385,7 @@ module.exports = class CoursesDBApi { } try { - const { rows, count } = await db.courses.findAndCountAll(queryOptions); + const { rows, count } = await db.pipeline_stages.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -632,7 +407,7 @@ module.exports = class CoursesDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'courses', + 'pipeline_stages', 'title', query, ), @@ -640,7 +415,7 @@ module.exports = class CoursesDBApi { }; } - const records = await db.courses.findAll({ + const records = await db.pipeline_stages.findAll({ attributes: [ 'id', 'title' ], where, limit: limit ? Number(limit) : undefined, diff --git a/backend/src/db/api/roles.js b/backend/src/db/api/roles.js index 4d11d0a..1571897 100644 --- a/backend/src/db/api/roles.js +++ b/backend/src/db/api/roles.js @@ -197,6 +197,7 @@ module.exports = class RolesDBApi { + output.permissions = await roles.getPermissions({ transaction }); diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js index 499d5df..2b043ad 100644 --- a/backend/src/db/api/users.js +++ b/backend/src/db/api/users.js @@ -403,17 +403,26 @@ module.exports = class UsersDBApi { - output.courses_instructor = await users.getCourses_instructor({ + + output.leads_owner = await users.getLeads_owner({ transaction }); - - output.enrollments_student = await users.getEnrollments_student({ + output.contacts_owner = await users.getContacts_owner({ transaction }); + output.deals_owner = await users.getDeals_owner({ + transaction + }); + + + output.activities_owner = await users.getActivities_owner({ + transaction + }); + output.avatar = await users.getAvatar({ diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js index b353587..b154b5f 100644 --- a/backend/src/db/db.config.js +++ b/backend/src/db/db.config.js @@ -15,7 +15,7 @@ module.exports = { username: 'postgres', dialect: 'postgres', password: '', - database: 'db_instructor_student_lms', + database: 'db_sales_pipeline_crm', host: process.env.DB_HOST || 'localhost', logging: console.log, seederStorage: 'sequelize', diff --git a/backend/src/db/migrations/1768904423064.js b/backend/src/db/migrations/1769169079630.js similarity index 76% rename from backend/src/db/migrations/1768904423064.js rename to backend/src/db/migrations/1769169079630.js index 9cfb6d5..1b1ed4c 100644 --- a/backend/src/db/migrations/1768904423064.js +++ b/backend/src/db/migrations/1769169079630.js @@ -108,7 +108,7 @@ module.exports = { - await queryInterface.createTable('courses', { + await queryInterface.createTable('pipeline_stages', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -140,7 +140,7 @@ module.exports = { - await queryInterface.createTable('lessons', { + await queryInterface.createTable('leads', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -172,7 +172,7 @@ module.exports = { - await queryInterface.createTable('enrollments', { + await queryInterface.createTable('contacts', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -204,7 +204,39 @@ module.exports = { - await queryInterface.createTable('progress', { + await queryInterface.createTable('deals', { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, { transaction }); + + + + await queryInterface.createTable('activities', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -495,7 +527,7 @@ module.exports = { await queryInterface.addColumn( - 'courses', + 'pipeline_stages', 'title', { type: Sequelize.DataTypes.TEXT, @@ -510,10 +542,10 @@ module.exports = { await queryInterface.addColumn( - 'courses', - 'description', + 'pipeline_stages', + 'order', { - type: Sequelize.DataTypes.TEXT, + type: Sequelize.DataTypes.INTEGER, @@ -525,35 +557,13 @@ module.exports = { await queryInterface.addColumn( - 'courses', - 'instructorId', + 'pipeline_stages', + 'probability', { - type: Sequelize.DataTypes.UUID, + type: Sequelize.DataTypes.INTEGER, - references: { - model: 'users', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'courses', - 'category', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['Programming','Design','Math','Language','Business','Other'], - - }, { transaction } ); @@ -562,27 +572,8 @@ module.exports = { await queryInterface.addColumn( - 'courses', - 'level', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['Beginner','Intermediate','Advanced'], - - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'courses', - 'published', + 'pipeline_stages', + 'is_default', { type: Sequelize.DataTypes.BOOLEAN, @@ -599,53 +590,8 @@ module.exports = { await queryInterface.addColumn( - 'courses', - 'start_date', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'courses', - 'end_date', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'courses', - 'price', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'courses', - 'language', + 'leads', + 'name', { type: Sequelize.DataTypes.TEXT, @@ -659,8 +605,8 @@ module.exports = { await queryInterface.addColumn( - 'lessons', - 'title', + 'leads', + 'company', { type: Sequelize.DataTypes.TEXT, @@ -674,8 +620,8 @@ module.exports = { await queryInterface.addColumn( - 'lessons', - 'content', + 'leads', + 'email', { type: Sequelize.DataTypes.TEXT, @@ -689,30 +635,10 @@ module.exports = { await queryInterface.addColumn( - 'lessons', - 'courseId', + 'leads', + 'phone', { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'courses', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'lessons', - 'order', - { - type: Sequelize.DataTypes.INTEGER, + type: Sequelize.DataTypes.TEXT, @@ -724,30 +650,13 @@ module.exports = { await queryInterface.addColumn( - 'lessons', - 'duration_minutes', + 'leads', + 'source', { - type: Sequelize.DataTypes.INTEGER, + type: Sequelize.DataTypes.ENUM, - - }, - { transaction } - ); - - - - - - - - - await queryInterface.addColumn( - 'lessons', - 'release_date', - { - type: Sequelize.DataTypes.DATE, - + values: ['Website','Referral','Email','ColdCall','SocialMedia'], }, @@ -758,13 +667,13 @@ module.exports = { await queryInterface.addColumn( - 'lessons', + 'leads', 'status', { type: Sequelize.DataTypes.ENUM, - values: ['draft','published','archived'], + values: ['New','Contacted','Qualified','Unqualified'], }, @@ -775,23 +684,8 @@ module.exports = { await queryInterface.addColumn( - 'enrollments', - 'enrollment_label', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'enrollments', - 'studentId', + 'leads', + 'ownerId', { type: Sequelize.DataTypes.UUID, @@ -810,60 +704,8 @@ module.exports = { await queryInterface.addColumn( - 'enrollments', - 'courseId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'courses', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'enrollments', - 'enrolled_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'enrollments', - 'status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['active','completed','cancelled'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'enrollments', - 'progress_percent', + 'leads', + 'estimated_value', { type: Sequelize.DataTypes.DECIMAL, @@ -877,23 +719,8 @@ module.exports = { await queryInterface.addColumn( - 'enrollments', - 'price_paid', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'progress', - 'note', + 'leads', + 'notes', { type: Sequelize.DataTypes.TEXT, @@ -907,15 +734,90 @@ module.exports = { await queryInterface.addColumn( - 'progress', - 'enrollmentId', + 'contacts', + 'name', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'contacts', + 'email', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'contacts', + 'phone', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'contacts', + 'title', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'contacts', + 'company', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'contacts', + 'ownerId', { type: Sequelize.DataTypes.UUID, references: { - model: 'enrollments', + model: 'users', key: 'id', }, @@ -927,15 +829,90 @@ module.exports = { await queryInterface.addColumn( - 'progress', - 'lessonId', + 'contacts', + 'notes', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'title', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'deal_number', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'value', + { + type: Sequelize.DataTypes.DECIMAL, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'currency', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'stageId', { type: Sequelize.DataTypes.UUID, references: { - model: 'lessons', + model: 'pipeline_stages', key: 'id', }, @@ -947,7 +924,156 @@ module.exports = { await queryInterface.addColumn( - 'progress', + 'deals', + 'status', + { + type: Sequelize.DataTypes.ENUM, + + + values: ['Open','Won','Lost'], + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'close_date', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'ownerId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'users', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'primary_contactId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'contacts', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'deals', + 'description', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'activities', + 'subject', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'activities', + 'activity_type', + { + type: Sequelize.DataTypes.ENUM, + + + values: ['Call','Meeting','Email','Task','FollowUp'], + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'activities', + 'start', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'activities', + 'end', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'activities', 'completed', { type: Sequelize.DataTypes.BOOLEAN, @@ -965,13 +1091,18 @@ module.exports = { await queryInterface.addColumn( - 'progress', - 'completed_at', + 'activities', + 'ownerId', { - type: Sequelize.DataTypes.DATE, + type: Sequelize.DataTypes.UUID, + references: { + model: 'users', + key: 'id', + }, + }, { transaction } ); @@ -980,13 +1111,18 @@ module.exports = { await queryInterface.addColumn( - 'progress', - 'score', + 'activities', + 'related_dealId', { - type: Sequelize.DataTypes.DECIMAL, + type: Sequelize.DataTypes.UUID, + references: { + model: 'deals', + key: 'id', + }, + }, { transaction } ); @@ -995,10 +1131,30 @@ module.exports = { await queryInterface.addColumn( - 'progress', - 'attempts', + 'activities', + 'related_leadId', { - type: Sequelize.DataTypes.INTEGER, + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'leads', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'activities', + 'notes', + { + type: Sequelize.DataTypes.TEXT, @@ -1029,31 +1185,39 @@ module.exports = { await queryInterface.removeColumn( - 'progress', - 'attempts', + 'activities', + 'notes', { transaction } ); await queryInterface.removeColumn( - 'progress', - 'score', + 'activities', + 'related_leadId', { transaction } ); await queryInterface.removeColumn( - 'progress', - 'completed_at', + 'activities', + 'related_dealId', { transaction } ); await queryInterface.removeColumn( - 'progress', + 'activities', + 'ownerId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'activities', 'completed', { transaction } ); @@ -1061,213 +1225,39 @@ module.exports = { await queryInterface.removeColumn( - 'progress', - 'lessonId', + 'activities', + 'end', { transaction } ); await queryInterface.removeColumn( - 'progress', - 'enrollmentId', + 'activities', + 'start', { transaction } ); await queryInterface.removeColumn( - 'progress', - 'note', + 'activities', + 'activity_type', { transaction } ); await queryInterface.removeColumn( - 'enrollments', - 'price_paid', + 'activities', + 'subject', { transaction } ); await queryInterface.removeColumn( - 'enrollments', - 'progress_percent', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'enrollments', - 'status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'enrollments', - 'enrolled_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'enrollments', - 'courseId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'enrollments', - 'studentId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'enrollments', - 'enrollment_label', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lessons', - 'status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lessons', - 'release_date', - { transaction } - ); - - - - - - - - await queryInterface.removeColumn( - 'lessons', - 'duration_minutes', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lessons', - 'order', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lessons', - 'courseId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lessons', - 'content', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'lessons', - 'title', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', - 'language', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', - 'price', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', - 'end_date', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', - 'start_date', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', - 'published', - { transaction } - ); - - - - - - await queryInterface.removeColumn( - 'courses', - 'level', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', - 'category', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', - 'instructorId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'courses', + 'deals', 'description', { transaction } ); @@ -1275,7 +1265,231 @@ module.exports = { await queryInterface.removeColumn( - 'courses', + 'deals', + 'primary_contactId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'ownerId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'close_date', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'status', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'stageId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'currency', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'value', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'deal_number', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'deals', + 'title', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'contacts', + 'notes', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'contacts', + 'ownerId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'contacts', + 'company', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'contacts', + 'title', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'contacts', + 'phone', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'contacts', + 'email', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'contacts', + 'name', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'notes', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'estimated_value', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'ownerId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'status', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'source', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'phone', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'email', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'company', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'leads', + 'name', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'pipeline_stages', + 'is_default', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'pipeline_stages', + 'probability', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'pipeline_stages', + 'order', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'pipeline_stages', 'title', { transaction } ); @@ -1416,19 +1630,23 @@ module.exports = { - await queryInterface.dropTable('progress', { transaction }); + await queryInterface.dropTable('activities', { transaction }); - await queryInterface.dropTable('enrollments', { transaction }); + await queryInterface.dropTable('deals', { transaction }); - await queryInterface.dropTable('lessons', { transaction }); + await queryInterface.dropTable('contacts', { transaction }); - await queryInterface.dropTable('courses', { transaction }); + await queryInterface.dropTable('leads', { transaction }); + + + + await queryInterface.dropTable('pipeline_stages', { transaction }); diff --git a/backend/src/db/models/enrollments.js b/backend/src/db/models/activities.js similarity index 61% rename from backend/src/db/models/enrollments.js rename to backend/src/db/models/activities.js index 3770caa..2a5f1cf 100644 --- a/backend/src/db/models/enrollments.js +++ b/backend/src/db/models/activities.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const enrollments = sequelize.define( - 'enrollments', + const activities = sequelize.define( + 'activities', { id: { type: DataTypes.UUID, @@ -14,48 +14,64 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -enrollment_label: { +subject: { type: DataTypes.TEXT, }, -enrolled_at: { - type: DataTypes.DATE, - - - - }, - -status: { +activity_type: { type: DataTypes.ENUM, values: [ -"active", +"Call", -"completed", +"Meeting", -"cancelled" +"Email", + + +"Task", + + +"FollowUp" ], }, -progress_percent: { - type: DataTypes.DECIMAL, +start: { + type: DataTypes.DATE, }, -price_paid: { - type: DataTypes.DECIMAL, +end: { + type: DataTypes.DATE, + + + + }, + +completed: { + type: DataTypes.BOOLEAN, + + allowNull: false, + defaultValue: false, + + + + }, + +notes: { + type: DataTypes.TEXT, @@ -74,7 +90,7 @@ price_paid: { }, ); - enrollments.associate = (db) => { + activities.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -86,13 +102,6 @@ price_paid: { - db.enrollments.hasMany(db.progress, { - as: 'progress_enrollment', - foreignKey: { - name: 'enrollmentId', - }, - constraints: false, - }); @@ -100,18 +109,26 @@ price_paid: { - db.enrollments.belongsTo(db.users, { - as: 'student', + db.activities.belongsTo(db.users, { + as: 'owner', foreignKey: { - name: 'studentId', + name: 'ownerId', }, constraints: false, }); - db.enrollments.belongsTo(db.courses, { - as: 'course', + db.activities.belongsTo(db.deals, { + as: 'related_deal', foreignKey: { - name: 'courseId', + name: 'related_dealId', + }, + constraints: false, + }); + + db.activities.belongsTo(db.leads, { + as: 'related_lead', + foreignKey: { + name: 'related_leadId', }, constraints: false, }); @@ -119,18 +136,18 @@ price_paid: { - db.enrollments.belongsTo(db.users, { + db.activities.belongsTo(db.users, { as: 'createdBy', }); - db.enrollments.belongsTo(db.users, { + db.activities.belongsTo(db.users, { as: 'updatedBy', }); }; - return enrollments; + return activities; }; diff --git a/backend/src/db/models/contacts.js b/backend/src/db/models/contacts.js new file mode 100644 index 0000000..cef74a2 --- /dev/null +++ b/backend/src/db/models/contacts.js @@ -0,0 +1,124 @@ +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, + }, + +name: { + type: DataTypes.TEXT, + + + + }, + +email: { + type: DataTypes.TEXT, + + + + }, + +phone: { + type: DataTypes.TEXT, + + + + }, + +title: { + type: DataTypes.TEXT, + + + + }, + +company: { + type: DataTypes.TEXT, + + + + }, + +notes: { + type: DataTypes.TEXT, + + + + }, + + 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.deals, { + as: 'deals_primary_contact', + foreignKey: { + name: 'primary_contactId', + }, + constraints: false, + }); + + + + +//end loop + + + + 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/lessons.js b/backend/src/db/models/deals.js similarity index 59% rename from backend/src/db/models/lessons.js rename to backend/src/db/models/deals.js index f640f92..f4c5dd4 100644 --- a/backend/src/db/models/lessons.js +++ b/backend/src/db/models/deals.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const lessons = sequelize.define( - 'lessons', + const deals = sequelize.define( + 'deals', { id: { type: DataTypes.UUID, @@ -21,29 +21,22 @@ title: { }, -content: { +deal_number: { type: DataTypes.TEXT, }, -order: { - type: DataTypes.INTEGER, +value: { + type: DataTypes.DECIMAL, }, -duration_minutes: { - type: DataTypes.INTEGER, - - - - }, - -release_date: { - type: DataTypes.DATE, +currency: { + type: DataTypes.TEXT, @@ -56,18 +49,32 @@ status: { values: [ -"draft", +"Open", -"published", +"Won", -"archived" +"Lost" ], }, +close_date: { + type: DataTypes.DATE, + + + + }, + +description: { + type: DataTypes.TEXT, + + + + }, + importHash: { type: DataTypes.STRING(255), allowNull: true, @@ -81,7 +88,7 @@ status: { }, ); - lessons.associate = (db) => { + deals.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -93,10 +100,11 @@ status: { - db.lessons.hasMany(db.progress, { - as: 'progress_lesson', + + db.deals.hasMany(db.activities, { + as: 'activities_related_deal', foreignKey: { - name: 'lessonId', + name: 'related_dealId', }, constraints: false, }); @@ -107,49 +115,45 @@ status: { - db.lessons.belongsTo(db.courses, { - as: 'course', + db.deals.belongsTo(db.pipeline_stages, { + as: 'stage', foreignKey: { - name: 'courseId', + name: 'stageId', + }, + constraints: false, + }); + + db.deals.belongsTo(db.users, { + as: 'owner', + foreignKey: { + name: 'ownerId', + }, + constraints: false, + }); + + db.deals.belongsTo(db.contacts, { + as: 'primary_contact', + foreignKey: { + name: 'primary_contactId', }, constraints: false, }); - db.lessons.hasMany(db.file, { - as: 'video_files', - foreignKey: 'belongsToId', - constraints: false, - scope: { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'video_files', - }, - }); - db.lessons.hasMany(db.file, { - as: 'resources', - foreignKey: 'belongsToId', - constraints: false, - scope: { - belongsTo: db.lessons.getTableName(), - belongsToColumn: 'resources', - }, - }); - - - db.lessons.belongsTo(db.users, { + db.deals.belongsTo(db.users, { as: 'createdBy', }); - db.lessons.belongsTo(db.users, { + db.deals.belongsTo(db.users, { as: 'updatedBy', }); }; - return lessons; + return deals; }; diff --git a/backend/src/db/models/courses.js b/backend/src/db/models/leads.js similarity index 54% rename from backend/src/db/models/courses.js rename to backend/src/db/models/leads.js index a20a3f4..d6ad732 100644 --- a/backend/src/db/models/courses.js +++ b/backend/src/db/models/leads.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const courses = sequelize.define( - 'courses', + const leads = sequelize.define( + 'leads', { id: { type: DataTypes.UUID, @@ -14,99 +14,89 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -title: { +name: { type: DataTypes.TEXT, }, -description: { +company: { type: DataTypes.TEXT, }, -category: { +email: { + type: DataTypes.TEXT, + + + + }, + +phone: { + type: DataTypes.TEXT, + + + + }, + +source: { type: DataTypes.ENUM, values: [ -"Programming", +"Website", -"Design", +"Referral", -"Math", +"Email", -"Language", +"ColdCall", -"Business", - - -"Other" +"SocialMedia" ], }, -level: { +status: { type: DataTypes.ENUM, values: [ -"Beginner", +"New", -"Intermediate", +"Contacted", -"Advanced" +"Qualified", + + +"Unqualified" ], }, -published: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - - - - }, - -start_date: { - type: DataTypes.DATE, - - - - }, - -end_date: { - type: DataTypes.DATE, - - - - }, - -price: { +estimated_value: { type: DataTypes.DECIMAL, }, -language: { +notes: { type: DataTypes.TEXT, @@ -126,7 +116,7 @@ language: { }, ); - courses.associate = (db) => { + leads.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -136,63 +126,46 @@ language: { - db.courses.hasMany(db.lessons, { - as: 'lessons_course', + + + + db.leads.hasMany(db.activities, { + as: 'activities_related_lead', foreignKey: { - name: 'courseId', + name: 'related_leadId', }, constraints: false, }); - db.courses.hasMany(db.enrollments, { - as: 'enrollments_course', - foreignKey: { - name: 'courseId', - }, - constraints: false, - }); - - - //end loop - db.courses.belongsTo(db.users, { - as: 'instructor', + db.leads.belongsTo(db.users, { + as: 'owner', foreignKey: { - name: 'instructorId', + name: 'ownerId', }, constraints: false, }); - db.courses.hasMany(db.file, { - as: 'thumbnail', - foreignKey: 'belongsToId', - constraints: false, - scope: { - belongsTo: db.courses.getTableName(), - belongsToColumn: 'thumbnail', - }, - }); - - db.courses.belongsTo(db.users, { + db.leads.belongsTo(db.users, { as: 'createdBy', }); - db.courses.belongsTo(db.users, { + db.leads.belongsTo(db.users, { as: 'updatedBy', }); }; - return courses; + return leads; }; diff --git a/backend/src/db/models/permissions.js b/backend/src/db/models/permissions.js index c6a1206..b39bf85 100644 --- a/backend/src/db/models/permissions.js +++ b/backend/src/db/models/permissions.js @@ -48,6 +48,7 @@ name: { + //end loop diff --git a/backend/src/db/models/progress.js b/backend/src/db/models/pipeline_stages.js similarity index 66% rename from backend/src/db/models/progress.js rename to backend/src/db/models/pipeline_stages.js index 60790b7..2bb43a4 100644 --- a/backend/src/db/models/progress.js +++ b/backend/src/db/models/pipeline_stages.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const progress = sequelize.define( - 'progress', + const pipeline_stages = sequelize.define( + 'pipeline_stages', { id: { type: DataTypes.UUID, @@ -14,14 +14,28 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -note: { +title: { type: DataTypes.TEXT, }, -completed: { +order: { + type: DataTypes.INTEGER, + + + + }, + +probability: { + type: DataTypes.INTEGER, + + + + }, + +is_default: { type: DataTypes.BOOLEAN, allowNull: false, @@ -29,27 +43,6 @@ completed: { - }, - -completed_at: { - type: DataTypes.DATE, - - - - }, - -score: { - type: DataTypes.DECIMAL, - - - - }, - -attempts: { - type: DataTypes.INTEGER, - - - }, importHash: { @@ -65,7 +58,7 @@ attempts: { }, ); - progress.associate = (db) => { + 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 @@ -77,43 +70,36 @@ attempts: { + db.pipeline_stages.hasMany(db.deals, { + as: 'deals_stage', + foreignKey: { + name: 'stageId', + }, + constraints: false, + }); + + //end loop - db.progress.belongsTo(db.enrollments, { - as: 'enrollment', - foreignKey: { - name: 'enrollmentId', - }, - constraints: false, - }); - - db.progress.belongsTo(db.lessons, { - as: 'lesson', - foreignKey: { - name: 'lessonId', - }, - constraints: false, - }); - - db.progress.belongsTo(db.users, { + db.pipeline_stages.belongsTo(db.users, { as: 'createdBy', }); - db.progress.belongsTo(db.users, { + db.pipeline_stages.belongsTo(db.users, { as: 'updatedBy', }); }; - return progress; + return pipeline_stages; }; diff --git a/backend/src/db/models/roles.js b/backend/src/db/models/roles.js index e26c222..3fd20a9 100644 --- a/backend/src/db/models/roles.js +++ b/backend/src/db/models/roles.js @@ -81,6 +81,7 @@ role_customization: { + //end loop diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js index b32822b..f6a6b72 100644 --- a/backend/src/db/models/users.js +++ b/backend/src/db/models/users.js @@ -144,25 +144,42 @@ provider: { - db.users.hasMany(db.courses, { - as: 'courses_instructor', + + db.users.hasMany(db.leads, { + as: 'leads_owner', foreignKey: { - name: 'instructorId', + name: 'ownerId', }, constraints: false, }); - - db.users.hasMany(db.enrollments, { - as: 'enrollments_student', + db.users.hasMany(db.contacts, { + as: 'contacts_owner', foreignKey: { - name: 'studentId', + name: 'ownerId', }, constraints: false, }); + db.users.hasMany(db.deals, { + as: 'deals_owner', + foreignKey: { + name: 'ownerId', + }, + constraints: false, + }); + + + db.users.hasMany(db.activities, { + as: 'activities_owner', + foreignKey: { + name: 'ownerId', + }, + constraints: false, + }); + //end loop diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index fcca1a3..a4dc74b 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -33,15 +33,15 @@ module.exports = { - { id: getId("PlatformManager"), name: "Platform Manager", createdAt, updatedAt }, + { id: getId("OrganizationOwner"), name: "Organization Owner", createdAt, updatedAt }, - { id: getId("InstructorLead"), name: "Instructor Lead", createdAt, updatedAt }, + { id: getId("SalesManager"), name: "Sales Manager", createdAt, updatedAt }, - { id: getId("Instructor"), name: "Instructor", createdAt, updatedAt }, + { id: getId("SeniorSalesRep"), name: "Senior Sales Rep", createdAt, updatedAt }, - { id: getId("TeachingAssistant"), name: "Teaching Assistant", createdAt, updatedAt }, + { id: getId("SalesRep"), name: "Sales Rep", createdAt, updatedAt }, - { id: getId("Student"), name: "Student", createdAt, updatedAt }, + { id: getId("SupportSpecialist"), name: "Support Specialist", createdAt, updatedAt }, @@ -61,7 +61,7 @@ module.exports = { } const entities = [ - "users","roles","permissions","courses","lessons","enrollments","progress",, + "users","roles","permissions","pipeline_stages","leads","contacts","deals","activities",, ]; await queryInterface.bulkInsert("permissions", entities.flatMap(createPermissions)); await queryInterface.bulkInsert("permissions", [{ id: getId(`READ_API_DOCS`), createdAt, updatedAt, name: `READ_API_DOCS` }]); @@ -90,19 +90,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('CREATE_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('CREATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('READ_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('READ_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('UPDATE_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('UPDATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('DELETE_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('DELETE_USERS') }, @@ -111,11 +111,11 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('CREATE_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('READ_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_USERS') }, @@ -130,7 +130,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('READ_USERS') }, @@ -145,7 +145,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('READ_USERS') }, @@ -160,7 +160,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('READ_USERS') }, @@ -185,19 +185,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('CREATE_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('CREATE_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('READ_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('READ_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('UPDATE_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('UPDATE_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('DELETE_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('DELETE_PIPELINE_STAGES') }, @@ -206,38 +206,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('CREATE_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('READ_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('UPDATE_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('DELETE_COURSES') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_COURSES') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_COURSES') }, - - + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('DELETE_PIPELINE_STAGES') }, @@ -248,7 +229,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('READ_PIPELINE_STAGES') }, @@ -263,7 +244,22 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('READ_PIPELINE_STAGES') }, + + + + + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('READ_PIPELINE_STAGES') }, @@ -286,19 +282,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('CREATE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('CREATE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('READ_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('READ_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('UPDATE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('UPDATE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('DELETE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('DELETE_LEADS') }, @@ -307,19 +303,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('CREATE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('READ_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('UPDATE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('DELETE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('DELETE_LEADS') }, @@ -328,15 +324,34 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('CREATE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('READ_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('UPDATE_LEADS') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('CREATE_LEADS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('READ_LEADS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('UPDATE_LEADS') }, @@ -349,24 +364,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_LESSONS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('UPDATE_LESSONS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('READ_LEADS') }, @@ -389,19 +387,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('CREATE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('CREATE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('READ_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('READ_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('UPDATE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('UPDATE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('DELETE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('DELETE_CONTACTS') }, @@ -410,19 +408,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('CREATE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('READ_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('UPDATE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('DELETE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('DELETE_CONTACTS') }, @@ -431,13 +429,141 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - - - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('CREATE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('READ_CONTACTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('UPDATE_CONTACTS') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('CREATE_CONTACTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('READ_CONTACTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('UPDATE_CONTACTS') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('CREATE_CONTACTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('READ_CONTACTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('UPDATE_CONTACTS') }, + + + + + + + + + + + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('CREATE_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('READ_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('UPDATE_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('DELETE_DEALS') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('DELETE_DEALS') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('CREATE_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('READ_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('UPDATE_DEALS') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('CREATE_DEALS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('READ_DEALS') }, + + @@ -450,24 +576,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_ENROLLMENTS') }, - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('CREATE_ENROLLMENTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('READ_DEALS') }, @@ -490,19 +599,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('CREATE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('CREATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('READ_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('READ_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('UPDATE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('UPDATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('DELETE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('DELETE_ACTIVITIES') }, @@ -511,19 +620,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('CREATE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('READ_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('READ_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('UPDATE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('UPDATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('DELETE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('DELETE_ACTIVITIES') }, @@ -532,34 +641,15 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('CREATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('READ_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('DELETE_PROGRESS') }, - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('READ_PROGRESS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('UPDATE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('UPDATE_ACTIVITIES') }, @@ -570,15 +660,38 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('CREATE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('CREATE_ACTIVITIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('READ_ACTIVITIES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('UPDATE_ACTIVITIES') }, + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('CREATE_ACTIVITIES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('READ_ACTIVITIES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('UPDATE_ACTIVITIES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('DELETE_ACTIVITIES') }, @@ -592,15 +705,15 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("PlatformManager"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("OrganizationOwner"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("InstructorLead"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesManager"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("SeniorSalesRep"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("TeachingAssistant"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("SalesRep"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("SupportSpecialist"), permissionId: getId('CREATE_SEARCH') }, @@ -620,25 +733,30 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PERMISSIONS') }, { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PERMISSIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_PIPELINE_STAGES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PIPELINE_STAGES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PIPELINE_STAGES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PIPELINE_STAGES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_LEADS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_LEADS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_LEADS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_LEADS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ENROLLMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_CONTACTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_CONTACTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_CONTACTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_CONTACTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PROGRESS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_DEALS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_DEALS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_DEALS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_DEALS') }, + + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ACTIVITIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ACTIVITIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ACTIVITIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ACTIVITIES') }, @@ -655,8 +773,8 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("PlatformManager")}' WHERE "email"='client@hello.com'`); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("InstructorLead")}' WHERE "email"='john@doe.com'`); + await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("OrganizationOwner")}' WHERE "email"='client@hello.com'`); + await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("SalesManager")}' WHERE "email"='john@doe.com'`); diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 8c58fb7..7482c63 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -28,13 +28,15 @@ const Users = db.users; -const Courses = db.courses; +const PipelineStages = db.pipeline_stages; -const Lessons = db.lessons; +const Leads = db.leads; -const Enrollments = db.enrollments; +const Contacts = db.contacts; -const Progress = db.progress; +const Deals = db.deals; + +const Activities = db.activities; @@ -42,277 +44,14 @@ const Progress = db.progress; -const CoursesData = [ +const PipelineStagesData = [ { - "title": "Introduction to JavaScript", - - - - - - - "description": "Foundational JavaScript concepts for web development.", - - - - - - - // type code here for "relation_one" field - - - - - - - "category": "Programming", - - - - - - - "level": "Advanced", - - - - - - - // type code here for "images" field - - - - - - - "published": true, - - - - - - - "start_date": new Date('2026-02-01T00:00:00Z'), - - - - - - - "end_date": new Date('2026-04-01T00:00:00Z'), - - - - - - - "price": 99.99, - - - - - - - "language": "English", - - - - }, - - { - - - - - "title": "Advanced React Patterns", - - - - - - - "description": "Deep dive into architecture and performance patterns in React.", - - - - - - - // type code here for "relation_one" field - - - - - - - "category": "Design", - - - - - - - "level": "Advanced", - - - - - - - // type code here for "images" field - - - - - - - "published": true, - - - - - - - "start_date": new Date('2026-03-01T00:00:00Z'), - - - - - - - "end_date": new Date('2026-05-01T00:00:00Z'), - - - - - - - "price": 149.99, - - - - - - - "language": "English", - - - - }, - - { - - - - - "title": "UI/UX Fundamentals", - - - - - - - "description": "Principles of user interface and experience design.", - - - - - - - // type code here for "relation_one" field - - - - - - - "category": "Business", - - - - - - - "level": "Intermediate", - - - - - - - // type code here for "images" field - - - - - - - "published": false, - - - - - - - "start_date": new Date('2026-04-01T00:00:00Z'), - - - - - - - "end_date": new Date('2026-06-01T00:00:00Z'), - - - - - - - "price": 79.0, - - - - - - - "language": "English", - - - - }, - -]; - - - -const LessonsData = [ - - { - - - - - "title": "Variables and Types", - - - - - - - "content": "Overview of JavaScript variables, primitives, and type coercion.", - - - - - - - // type code here for "relation_one" field + "title": "Qualification", @@ -326,35 +65,14 @@ const LessonsData = [ - "duration_minutes": 22, + "probability": 20, - // type code here for "files" field - - - - - - - // type code here for "files" field - - - - - - - "release_date": new Date('2026-02-01T09:00:00Z'), - - - - - - - "status": "draft", + "is_default": true, @@ -365,88 +83,7 @@ const LessonsData = [ - "title": "Asynchronous JavaScript", - - - - - - - "content": "Promises, async await, and managing asynchronous flow.", - - - - - - - // type code here for "relation_one" field - - - - - - - "order": 4, - - - - - - - "duration_minutes": 35, - - - - - - - // type code here for "files" field - - - - - - - // type code here for "files" field - - - - - - - "release_date": new Date('2026-02-08T09:00:00Z'), - - - - - - - "status": "draft", - - - - }, - - { - - - - - "title": "Hooks Deep Dive", - - - - - - - "content": "Custom hooks, useReducer, and performance optimization techniques.", - - - - - - - // type code here for "relation_one" field + "title": "Proposal", @@ -460,35 +97,110 @@ const LessonsData = [ - "duration_minutes": 48, + "probability": 50, - // type code here for "files" field + "is_default": false, + + + + }, + + { + + + + + "title": "Negotiation", - // type code here for "files" field + "order": 3, - "release_date": new Date('2026-03-05T09:00:00Z'), + "probability": 70, - "status": "published", + "is_default": true, + + + + }, + + { + + + + + "title": "Closed Won", + + + + + + + "order": 4, + + + + + + + "probability": 100, + + + + + + + "is_default": false, + + + + }, + + { + + + + + "title": "Closed Lost", + + + + + + + "order": 5, + + + + + + + "probability": 0, + + + + + + + "is_default": true, @@ -498,14 +210,49 @@ const LessonsData = [ -const EnrollmentsData = [ +const LeadsData = [ { - "enrollment_label": "Liam - JavaScript Intro", + "name": "Emma Stone", + + + + + + + "company": "BrightWave Solutions", + + + + + + + "email": "emma.stone@brightwave.com", + + + + + + + "phone": "+1-415-555-0201", + + + + + + + "source": "Referral", + + + + + + + "status": "Contacted", @@ -519,35 +266,14 @@ const EnrollmentsData = [ - // type code here for "relation_one" field + "estimated_value": 7500.0, - "enrolled_at": new Date('2026-01-20T10:00:00Z'), - - - - - - - "status": "completed", - - - - - - - "progress_percent": 45.0, - - - - - - - "price_paid": 99.99, + "notes": "Requested demo and pricing details for Q2 rollout.", @@ -558,7 +284,42 @@ const EnrollmentsData = [ - "enrollment_label": "Sara - React Advanced", + "name": "Liam Chen", + + + + + + + "company": "Apex Retail", + + + + + + + "email": "liam.chen@apexretail.com", + + + + + + + "phone": "+1-415-555-0202", + + + + + + + "source": "SocialMedia", + + + + + + + "status": "New", @@ -572,35 +333,14 @@ const EnrollmentsData = [ - // type code here for "relation_one" field + "estimated_value": 42000.0, - "enrolled_at": new Date('2026-02-25T11:30:00Z'), - - - - - - - "status": "cancelled", - - - - - - - "progress_percent": 12.5, - - - - - - - "price_paid": 149.99, + "notes": "Referred by partner; interested in multi-store license.", @@ -611,7 +351,42 @@ const EnrollmentsData = [ - "enrollment_label": "Liam - UIUX Fundamentals", + "name": "Olivia Martinez", + + + + + + + "company": "GreenFields Agri", + + + + + + + "email": "olivia.martinez@greenfields.com", + + + + + + + "phone": "+1-415-555-0203", + + + + + + + "source": "SocialMedia", + + + + + + + "status": "Contacted", @@ -625,6 +400,66 @@ const EnrollmentsData = [ + "estimated_value": 18000.0, + + + + + + + "notes": "Budget approved; evaluating implementation timeline.", + + + + }, + + { + + + + + "name": "Noah Patel", + + + + + + + "company": "VoltTech", + + + + + + + "email": "noah.patel@volttech.io", + + + + + + + "phone": "+1-415-555-0204", + + + + + + + "source": "Website", + + + + + + + "status": "Unqualified", + + + + + + // type code here for "relation_one" field @@ -632,28 +467,81 @@ const EnrollmentsData = [ - "enrolled_at": new Date('2026-04-02T08:00:00Z'), + "estimated_value": 0.0, - "status": "completed", + "notes": "Not a fit due to product constraints at this time.", + + + + }, + + { + + + + + "name": "Ava Johnson", - "progress_percent": 0.0, + "company": "Blue Harbor Media", - "price_paid": 0.0, + "email": "ava.johnson@blueharbor.com", + + + + + + + "phone": "+1-415-555-0205", + + + + + + + "source": "ColdCall", + + + + + + + "status": "New", + + + + + + + // type code here for "relation_one" field + + + + + + + "estimated_value": 9600.0, + + + + + + + "notes": "Requested case studies and reference customers.", @@ -663,14 +551,42 @@ const EnrollmentsData = [ -const ProgressData = [ +const ContactsData = [ { - "note": "Completed variables and types lesson with practice exercises", + "name": "Daniel Kim", + + + + + + + "email": "daniel.kim@brightwave.com", + + + + + + + "phone": "+1-415-555-0301", + + + + + + + "title": "Head of Operations", + + + + + + + "company": "BrightWave Solutions", @@ -684,35 +600,7 @@ const ProgressData = [ - // type code here for "relation_one" field - - - - - - - "completed": true, - - - - - - - "completed_at": new Date('2026-02-02T16:20:00Z'), - - - - - - - "score": 92.5, - - - - - - - "attempts": 1, + "notes": "Primary contact for implementation discussions.", @@ -723,7 +611,267 @@ const ProgressData = [ - "note": "Watched async lesson, needs review of promises", + "name": "Maya Singh", + + + + + + + "email": "maya.singh@apexretail.com", + + + + + + + "phone": "+1-415-555-0302", + + + + + + + "title": "Procurement Manager", + + + + + + + "company": "Apex Retail", + + + + + + + // type code here for "relation_one" field + + + + + + + "notes": "Handles contracts and procurement approvals.", + + + + }, + + { + + + + + "name": "Carlos Gomez", + + + + + + + "email": "carlos.gomez@greenfields.com", + + + + + + + "phone": "+1-415-555-0303", + + + + + + + "title": "IT Director", + + + + + + + "company": "GreenFields Agri", + + + + + + + // type code here for "relation_one" field + + + + + + + "notes": "Interested in integration details and API support.", + + + + }, + + { + + + + + "name": "Hannah Park", + + + + + + + "email": "hannah.park@volttech.io", + + + + + + + "phone": "+1-415-555-0304", + + + + + + + "title": "CTO", + + + + + + + "company": "VoltTech", + + + + + + + // type code here for "relation_one" field + + + + + + + "notes": "Decided not to pursue due to competing priorities.", + + + + }, + + { + + + + + "name": "Ethan Brooks", + + + + + + + "email": "ethan.brooks@blueharbor.com", + + + + + + + "phone": "+1-415-555-0305", + + + + + + + "title": "Marketing Director", + + + + + + + "company": "Blue Harbor Media", + + + + + + + // type code here for "relation_one" field + + + + + + + "notes": "Interested in a pilot for Q3 marketing campaigns.", + + + + }, + +]; + + + +const DealsData = [ + + { + + + + + "title": "BrightWave Platform Expansion", + + + + + + + "deal_number": "DW-1001", + + + + + + + "value": 7500.0, + + + + + + + "currency": "USD", + + + + + + + // type code here for "relation_one" field + + + + + + + "status": "Lost", + + + + + + + "close_date": new Date('2026-03-15T17:00:00Z'), @@ -744,6 +892,347 @@ const ProgressData = [ + "description": "Expansion of existing deployment to include analytics module.", + + + + }, + + { + + + + + "title": "Apex National Rollout", + + + + + + + "deal_number": "DW-1002", + + + + + + + "value": 42000.0, + + + + + + + "currency": "USD", + + + + + + + // type code here for "relation_one" field + + + + + + + "status": "Won", + + + + + + + "close_date": new Date('2026-05-01T17:00:00Z'), + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "description": "Multi-store license with priority support and integrations.", + + + + }, + + { + + + + + "title": "GreenFields Proof of Concept", + + + + + + + "deal_number": "DW-1003", + + + + + + + "value": 18000.0, + + + + + + + "currency": "USD", + + + + + + + // type code here for "relation_one" field + + + + + + + "status": "Open", + + + + + + + "close_date": new Date('2026-02-28T17:00:00Z'), + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "description": "6 week POC to validate workflows and integrations.", + + + + }, + + { + + + + + "title": "VoltTech Evaluation", + + + + + + + "deal_number": "DW-1004", + + + + + + + "value": 0.0, + + + + + + + "currency": "USD", + + + + + + + // type code here for "relation_one" field + + + + + + + "status": "Lost", + + + + + + + "close_date": new Date('2026-01-10T17:00:00Z'), + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "description": "Opportunity closed lost due to feature mismatch.", + + + + }, + + { + + + + + "title": "Blue Harbor Pilot", + + + + + + + "deal_number": "DW-1005", + + + + + + + "value": 9600.0, + + + + + + + "currency": "USD", + + + + + + + // type code here for "relation_one" field + + + + + + + "status": "Lost", + + + + + + + "close_date": new Date('2026-04-20T17:00:00Z'), + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "description": "Pilot program for marketing automation integration.", + + + + }, + +]; + + + +const ActivitiesData = [ + + { + + + + + "subject": "Introductory Demo with BrightWave", + + + + + + + "activity_type": "Meeting", + + + + + + + "start": new Date('2026-01-28T15:00:00Z'), + + + + + + + "end": new Date('2026-01-28T16:00:00Z'), + + + + + + "completed": false, @@ -751,21 +1240,28 @@ const ProgressData = [ - "completed_at": new Date('2020-01-01T00:00:00Z'), + // type code here for "relation_one" field - "score": 0.0, + // type code here for "relation_one" field - "attempts": 0, + // type code here for "relation_one" field + + + + + + + "notes": "Demo scheduled to showcase analytics capabilities.", @@ -776,21 +1272,28 @@ const ProgressData = [ - "note": "Reviewed hooks and implemented custom hook example", + "subject": "Contract Review with Apex", - // type code here for "relation_one" field + "activity_type": "Email", - // type code here for "relation_one" field + "start": new Date('2026-02-10T14:00:00Z'), + + + + + + + "end": new Date('2026-02-10T14:30:00Z'), @@ -804,21 +1307,229 @@ const ProgressData = [ - "completed_at": new Date('2026-03-10T12:00:00Z'), + // type code here for "relation_one" field - "score": 88.0, + // type code here for "relation_one" field - "attempts": 1, + // type code here for "relation_one" field + + + + + + + "notes": "Discussion on procurement terms and SLAs.", + + + + }, + + { + + + + + "subject": "POC Kickoff for GreenFields", + + + + + + + "activity_type": "Email", + + + + + + + "start": new Date('2026-02-02T09:00:00Z'), + + + + + + + "end": new Date('2026-02-02T10:00:00Z'), + + + + + + + "completed": true, + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "notes": "Define success criteria and integration points.", + + + + }, + + { + + + + + "subject": "Post-loss Feedback for VoltTech", + + + + + + + "activity_type": "Meeting", + + + + + + + "start": new Date('2026-01-15T11:00:00Z'), + + + + + + + "end": new Date('2026-01-15T11:30:00Z'), + + + + + + + "completed": true, + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "notes": "Captured feedback for product roadmap consideration.", + + + + }, + + { + + + + + "subject": "Pilot Planning with Blue Harbor", + + + + + + + "activity_type": "Call", + + + + + + + "start": new Date('2026-03-05T08:00:00Z'), + + + + + + + "end": new Date('2026-03-05T08:05:00Z'), + + + + + + + "completed": true, + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "notes": "Shared pilot plan and next steps via email.", @@ -875,49 +1586,97 @@ const ProgressData = [ + + + + + + + + + + + + + + + + + + + + - async function associateCoursWithInstructor() { + async function associateLeadWithOwner() { - const relatedInstructor0 = await Users.findOne({ + const relatedOwner0 = await Users.findOne({ offset: Math.floor(Math.random() * (await Users.count())), }); - const Cours0 = await Courses.findOne({ + const Lead0 = await Leads.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Cours0?.setInstructor) + if (Lead0?.setOwner) { await - Cours0. - setInstructor(relatedInstructor0); + Lead0. + setOwner(relatedOwner0); } - const relatedInstructor1 = await Users.findOne({ + const relatedOwner1 = await Users.findOne({ offset: Math.floor(Math.random() * (await Users.count())), }); - const Cours1 = await Courses.findOne({ + const Lead1 = await Leads.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Cours1?.setInstructor) + if (Lead1?.setOwner) { await - Cours1. - setInstructor(relatedInstructor1); + Lead1. + setOwner(relatedOwner1); } - const relatedInstructor2 = await Users.findOne({ + const relatedOwner2 = await Users.findOne({ offset: Math.floor(Math.random() * (await Users.count())), }); - const Cours2 = await Courses.findOne({ + const Lead2 = await Leads.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Cours2?.setInstructor) + if (Lead2?.setOwner) { await - Cours2. - setInstructor(relatedInstructor2); + Lead2. + setOwner(relatedOwner2); + } + + const relatedOwner3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Lead3 = await Leads.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Lead3?.setOwner) + { + await + Lead3. + setOwner(relatedOwner3); + } + + const relatedOwner4 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Lead4 = await Leads.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Lead4?.setOwner) + { + await + Lead4. + setOwner(relatedOwner4); } } @@ -927,18 +1686,6 @@ const ProgressData = [ - - - - - - - - - - - - @@ -947,64 +1694,88 @@ const ProgressData = [ + + + + + + - async function associateLessonWithCourse() { + async function associateContactWithOwner() { - const relatedCourse0 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), + const relatedOwner0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Lesson0 = await Lessons.findOne({ + const Contact0 = await Contacts.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Lesson0?.setCourse) + if (Contact0?.setOwner) { await - Lesson0. - setCourse(relatedCourse0); + Contact0. + setOwner(relatedOwner0); } - const relatedCourse1 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), + const relatedOwner1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Lesson1 = await Lessons.findOne({ + const Contact1 = await Contacts.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Lesson1?.setCourse) + if (Contact1?.setOwner) { await - Lesson1. - setCourse(relatedCourse1); + Contact1. + setOwner(relatedOwner1); } - const relatedCourse2 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), + const relatedOwner2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Lesson2 = await Lessons.findOne({ + const Contact2 = await Contacts.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Lesson2?.setCourse) + if (Contact2?.setOwner) { await - Lesson2. - setCourse(relatedCourse2); + Contact2. + setOwner(relatedOwner2); + } + + const relatedOwner3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Contact3 = await Contacts.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Contact3?.setOwner) + { + await + Contact3. + setOwner(relatedOwner3); + } + + const relatedOwner4 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Contact4 = await Contacts.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Contact4?.setOwner) + { + await + Contact4. + setOwner(relatedOwner4); } } - - - - - - - - - - @@ -1015,98 +1786,83 @@ const ProgressData = [ - - async function associateEnrollmentWithStudent() { - - const relatedStudent0 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment0 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Enrollment0?.setStudent) - { - await - Enrollment0. - setStudent(relatedStudent0); - } - - const relatedStudent1 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment1 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Enrollment1?.setStudent) - { - await - Enrollment1. - setStudent(relatedStudent1); - } - - const relatedStudent2 = await Users.findOne({ - offset: Math.floor(Math.random() * (await Users.count())), - }); - const Enrollment2 = await Enrollments.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Enrollment2?.setStudent) - { - await - Enrollment2. - setStudent(relatedStudent2); - } - - } + + + - async function associateEnrollmentWithCourse() { + async function associateDealWithStage() { - const relatedCourse0 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), + const relatedStage0 = await PipelineStages.findOne({ + offset: Math.floor(Math.random() * (await PipelineStages.count())), }); - const Enrollment0 = await Enrollments.findOne({ + const Deal0 = await Deals.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Enrollment0?.setCourse) + if (Deal0?.setStage) { await - Enrollment0. - setCourse(relatedCourse0); + Deal0. + setStage(relatedStage0); } - const relatedCourse1 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), + const relatedStage1 = await PipelineStages.findOne({ + offset: Math.floor(Math.random() * (await PipelineStages.count())), }); - const Enrollment1 = await Enrollments.findOne({ + const Deal1 = await Deals.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Enrollment1?.setCourse) + if (Deal1?.setStage) { await - Enrollment1. - setCourse(relatedCourse1); + Deal1. + setStage(relatedStage1); } - const relatedCourse2 = await Courses.findOne({ - offset: Math.floor(Math.random() * (await Courses.count())), + const relatedStage2 = await PipelineStages.findOne({ + offset: Math.floor(Math.random() * (await PipelineStages.count())), }); - const Enrollment2 = await Enrollments.findOne({ + const Deal2 = await Deals.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Enrollment2?.setCourse) + if (Deal2?.setStage) { await - Enrollment2. - setCourse(relatedCourse2); + Deal2. + setStage(relatedStage2); + } + + const relatedStage3 = await PipelineStages.findOne({ + offset: Math.floor(Math.random() * (await PipelineStages.count())), + }); + const Deal3 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Deal3?.setStage) + { + await + Deal3. + setStage(relatedStage3); + } + + const relatedStage4 = await PipelineStages.findOne({ + offset: Math.floor(Math.random() * (await PipelineStages.count())), + }); + const Deal4 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Deal4?.setStage) + { + await + Deal4. + setStage(relatedStage4); } } @@ -1117,6 +1873,158 @@ const ProgressData = [ + + async function associateDealWithOwner() { + + const relatedOwner0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Deal0 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Deal0?.setOwner) + { + await + Deal0. + setOwner(relatedOwner0); + } + + const relatedOwner1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Deal1 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Deal1?.setOwner) + { + await + Deal1. + setOwner(relatedOwner1); + } + + const relatedOwner2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Deal2 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Deal2?.setOwner) + { + await + Deal2. + setOwner(relatedOwner2); + } + + const relatedOwner3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Deal3 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Deal3?.setOwner) + { + await + Deal3. + setOwner(relatedOwner3); + } + + const relatedOwner4 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Deal4 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Deal4?.setOwner) + { + await + Deal4. + setOwner(relatedOwner4); + } + + } + + + + + async function associateDealWithPrimary_contact() { + + const relatedPrimary_contact0 = await Contacts.findOne({ + offset: Math.floor(Math.random() * (await Contacts.count())), + }); + const Deal0 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Deal0?.setPrimary_contact) + { + await + Deal0. + setPrimary_contact(relatedPrimary_contact0); + } + + const relatedPrimary_contact1 = await Contacts.findOne({ + offset: Math.floor(Math.random() * (await Contacts.count())), + }); + const Deal1 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Deal1?.setPrimary_contact) + { + await + Deal1. + setPrimary_contact(relatedPrimary_contact1); + } + + const relatedPrimary_contact2 = await Contacts.findOne({ + offset: Math.floor(Math.random() * (await Contacts.count())), + }); + const Deal2 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Deal2?.setPrimary_contact) + { + await + Deal2. + setPrimary_contact(relatedPrimary_contact2); + } + + const relatedPrimary_contact3 = await Contacts.findOne({ + offset: Math.floor(Math.random() * (await Contacts.count())), + }); + const Deal3 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Deal3?.setPrimary_contact) + { + await + Deal3. + setPrimary_contact(relatedPrimary_contact3); + } + + const relatedPrimary_contact4 = await Contacts.findOne({ + offset: Math.floor(Math.random() * (await Contacts.count())), + }); + const Deal4 = await Deals.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Deal4?.setPrimary_contact) + { + await + Deal4. + setPrimary_contact(relatedPrimary_contact4); + } + + } + @@ -1127,49 +2035,85 @@ const ProgressData = [ + + + + + + + + - async function associateProgresWithEnrollment() { + async function associateActivityWithOwner() { - const relatedEnrollment0 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), + const relatedOwner0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Progres0 = await Progress.findOne({ + const Activity0 = await Activities.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Progres0?.setEnrollment) + if (Activity0?.setOwner) { await - Progres0. - setEnrollment(relatedEnrollment0); + Activity0. + setOwner(relatedOwner0); } - const relatedEnrollment1 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), + const relatedOwner1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Progres1 = await Progress.findOne({ + const Activity1 = await Activities.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Progres1?.setEnrollment) + if (Activity1?.setOwner) { await - Progres1. - setEnrollment(relatedEnrollment1); + Activity1. + setOwner(relatedOwner1); } - const relatedEnrollment2 = await Enrollments.findOne({ - offset: Math.floor(Math.random() * (await Enrollments.count())), + const relatedOwner2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Progres2 = await Progress.findOne({ + const Activity2 = await Activities.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Progres2?.setEnrollment) + if (Activity2?.setOwner) { await - Progres2. - setEnrollment(relatedEnrollment2); + Activity2. + setOwner(relatedOwner2); + } + + const relatedOwner3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Activity3 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Activity3?.setOwner) + { + await + Activity3. + setOwner(relatedOwner3); + } + + const relatedOwner4 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Activity4 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Activity4?.setOwner) + { + await + Activity4. + setOwner(relatedOwner4); } } @@ -1177,57 +2121,156 @@ const ProgressData = [ - async function associateProgresWithLesson() { + async function associateActivityWithRelated_deal() { - const relatedLesson0 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), + const relatedRelated_deal0 = await Deals.findOne({ + offset: Math.floor(Math.random() * (await Deals.count())), }); - const Progres0 = await Progress.findOne({ + const Activity0 = await Activities.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Progres0?.setLesson) + if (Activity0?.setRelated_deal) { await - Progres0. - setLesson(relatedLesson0); + Activity0. + setRelated_deal(relatedRelated_deal0); } - const relatedLesson1 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), + const relatedRelated_deal1 = await Deals.findOne({ + offset: Math.floor(Math.random() * (await Deals.count())), }); - const Progres1 = await Progress.findOne({ + const Activity1 = await Activities.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Progres1?.setLesson) + if (Activity1?.setRelated_deal) { await - Progres1. - setLesson(relatedLesson1); + Activity1. + setRelated_deal(relatedRelated_deal1); } - const relatedLesson2 = await Lessons.findOne({ - offset: Math.floor(Math.random() * (await Lessons.count())), + const relatedRelated_deal2 = await Deals.findOne({ + offset: Math.floor(Math.random() * (await Deals.count())), }); - const Progres2 = await Progress.findOne({ + const Activity2 = await Activities.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Progres2?.setLesson) + if (Activity2?.setRelated_deal) { await - Progres2. - setLesson(relatedLesson2); + Activity2. + setRelated_deal(relatedRelated_deal2); + } + + const relatedRelated_deal3 = await Deals.findOne({ + offset: Math.floor(Math.random() * (await Deals.count())), + }); + const Activity3 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Activity3?.setRelated_deal) + { + await + Activity3. + setRelated_deal(relatedRelated_deal3); + } + + const relatedRelated_deal4 = await Deals.findOne({ + offset: Math.floor(Math.random() * (await Deals.count())), + }); + const Activity4 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Activity4?.setRelated_deal) + { + await + Activity4. + setRelated_deal(relatedRelated_deal4); } } - - - + + async function associateActivityWithRelated_lead() { + + const relatedRelated_lead0 = await Leads.findOne({ + offset: Math.floor(Math.random() * (await Leads.count())), + }); + const Activity0 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Activity0?.setRelated_lead) + { + await + Activity0. + setRelated_lead(relatedRelated_lead0); + } + + const relatedRelated_lead1 = await Leads.findOne({ + offset: Math.floor(Math.random() * (await Leads.count())), + }); + const Activity1 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Activity1?.setRelated_lead) + { + await + Activity1. + setRelated_lead(relatedRelated_lead1); + } + + const relatedRelated_lead2 = await Leads.findOne({ + offset: Math.floor(Math.random() * (await Leads.count())), + }); + const Activity2 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Activity2?.setRelated_lead) + { + await + Activity2. + setRelated_lead(relatedRelated_lead2); + } + + const relatedRelated_lead3 = await Leads.findOne({ + offset: Math.floor(Math.random() * (await Leads.count())), + }); + const Activity3 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Activity3?.setRelated_lead) + { + await + Activity3. + setRelated_lead(relatedRelated_lead3); + } + + const relatedRelated_lead4 = await Leads.findOne({ + offset: Math.floor(Math.random() * (await Leads.count())), + }); + const Activity4 = await Activities.findOne({ + order: [['id', 'ASC']], + offset: 4 + }); + if (Activity4?.setRelated_lead) + { + await + Activity4. + setRelated_lead(relatedRelated_lead4); + } + + } @@ -1243,22 +2286,27 @@ module.exports = { - await Courses.bulkCreate(CoursesData); + await PipelineStages.bulkCreate(PipelineStagesData); - await Lessons.bulkCreate(LessonsData); + await Leads.bulkCreate(LeadsData); - await Enrollments.bulkCreate(EnrollmentsData); + await Contacts.bulkCreate(ContactsData); - await Progress.bulkCreate(ProgressData); + await Deals.bulkCreate(DealsData); + + + + + await Activities.bulkCreate(ActivitiesData); await Promise.all([ @@ -1306,21 +2354,28 @@ module.exports = { + + + + + + + + + + + + + + + + + + + - await associateCoursWithInstructor(), - - - - - - - - - - - - + await associateLeadWithOwner(), @@ -1335,18 +2390,14 @@ module.exports = { + + + + + + - await associateLessonWithCourse(), - - - - - - - - - - + await associateContactWithOwner(), @@ -1357,13 +2408,14 @@ module.exports = { - - await associateEnrollmentWithStudent(), + + + - await associateEnrollmentWithCourse(), + await associateDealWithStage(), @@ -1371,6 +2423,14 @@ module.exports = { + + await associateDealWithOwner(), + + + + + await associateDealWithPrimary_contact(), + @@ -1380,19 +2440,26 @@ module.exports = { + + + + + + + + - await associateProgresWithEnrollment(), + await associateActivityWithOwner(), - await associateProgresWithLesson(), + await associateActivityWithRelated_deal(), - - - + + await associateActivityWithRelated_lead(), @@ -1409,16 +2476,19 @@ module.exports = { - await queryInterface.bulkDelete('courses', null, {}); + await queryInterface.bulkDelete('pipeline_stages', null, {}); - await queryInterface.bulkDelete('lessons', null, {}); + await queryInterface.bulkDelete('leads', null, {}); - await queryInterface.bulkDelete('enrollments', null, {}); + await queryInterface.bulkDelete('contacts', null, {}); - await queryInterface.bulkDelete('progress', null, {}); + await queryInterface.bulkDelete('deals', null, {}); + + + await queryInterface.bulkDelete('activities', null, {}); }, diff --git a/backend/src/index.js b/backend/src/index.js index 4012ef6..67c4161 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -14,6 +14,7 @@ const swaggerJsDoc = require('swagger-jsdoc'); const authRoutes = require('./routes/auth'); const fileRoutes = require('./routes/file'); const searchRoutes = require('./routes/search'); +const sqlRoutes = require('./routes/sql'); const pexelsRoutes = require('./routes/pexels'); const openaiRoutes = require('./routes/openai'); @@ -26,13 +27,15 @@ const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); -const coursesRoutes = require('./routes/courses'); +const pipeline_stagesRoutes = require('./routes/pipeline_stages'); -const lessonsRoutes = require('./routes/lessons'); +const leadsRoutes = require('./routes/leads'); -const enrollmentsRoutes = require('./routes/enrollments'); +const contactsRoutes = require('./routes/contacts'); -const progressRoutes = require('./routes/progress'); +const dealsRoutes = require('./routes/deals'); + +const activitiesRoutes = require('./routes/activities'); const getBaseUrl = (url) => { @@ -45,8 +48,8 @@ const options = { openapi: "3.0.0", info: { version: "1.0.0", - title: "Instructor-Student LMS", - description: "Instructor-Student LMS Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.", + title: "Sales Pipeline CRM", + description: "Sales Pipeline CRM Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.", }, servers: [ { @@ -98,13 +101,15 @@ app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoute app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes); -app.use('/api/courses', passport.authenticate('jwt', {session: false}), coursesRoutes); +app.use('/api/pipeline_stages', passport.authenticate('jwt', {session: false}), pipeline_stagesRoutes); -app.use('/api/lessons', passport.authenticate('jwt', {session: false}), lessonsRoutes); +app.use('/api/leads', passport.authenticate('jwt', {session: false}), leadsRoutes); -app.use('/api/enrollments', passport.authenticate('jwt', {session: false}), enrollmentsRoutes); +app.use('/api/contacts', passport.authenticate('jwt', {session: false}), contactsRoutes); -app.use('/api/progress', passport.authenticate('jwt', {session: false}), progressRoutes); +app.use('/api/deals', passport.authenticate('jwt', {session: false}), dealsRoutes); + +app.use('/api/activities', passport.authenticate('jwt', {session: false}), activitiesRoutes); app.use( '/api/openai', @@ -121,6 +126,10 @@ app.use( '/api/search', passport.authenticate('jwt', { session: false }), searchRoutes); +app.use( + '/api/sql', + passport.authenticate('jwt', { session: false }), + sqlRoutes); const publicDir = path.join( diff --git a/backend/src/routes/lessons.js b/backend/src/routes/activities.js similarity index 76% rename from backend/src/routes/lessons.js rename to backend/src/routes/activities.js index 290ad48..4aba19c 100644 --- a/backend/src/routes/lessons.js +++ b/backend/src/routes/activities.js @@ -1,8 +1,8 @@ const express = require('express'); -const LessonsService = require('../services/lessons'); -const LessonsDBApi = require('../db/api/lessons'); +const ActivitiesService = require('../services/activities'); +const ActivitiesDBApi = require('../db/api/activities'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,30 +15,24 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('lessons')); +router.use(checkCrudPermissions('activities')); /** * @swagger * components: * schemas: - * Lessons: + * Activities: * type: object * properties: - * title: + * subject: * type: string - * default: title - * content: + * default: subject + * notes: * type: string - * default: content + * default: notes - * order: - * type: integer - * format: int64 - * duration_minutes: - * type: integer - * format: int64 * @@ -47,17 +41,17 @@ router.use(checkCrudPermissions('lessons')); /** * @swagger * tags: - * name: Lessons - * description: The Lessons managing API + * name: Activities + * description: The Activities managing API */ /** * @swagger -* /api/lessons: +* /api/activities: * post: * security: * - bearerAuth: [] -* tags: [Lessons] +* tags: [Activities] * summary: Add new item * description: Add new item * requestBody: @@ -69,14 +63,14 @@ router.use(checkCrudPermissions('lessons')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Lessons" +* $ref: "#/components/schemas/Activities" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Lessons" +* $ref: "#/components/schemas/Activities" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -87,7 +81,7 @@ router.use(checkCrudPermissions('lessons')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await LessonsService.create(req.body.data, req.currentUser, true, link.host); + await ActivitiesService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -98,7 +92,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Lessons] + * tags: [Activities] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -111,14 +105,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -130,18 +124,18 @@ router.post('/', wrapAsync(async (req, res) => { 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 LessonsService.bulkImport(req, res, true, link.host); + await ActivitiesService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/lessons/{id}: + * /api/activities/{id}: * put: * security: * - bearerAuth: [] - * tags: [Lessons] + * tags: [Activities] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -164,7 +158,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * required: * - id * responses: @@ -173,7 +167,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 400: * description: Invalid ID supplied * 401: @@ -184,18 +178,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await LessonsService.update(req.body.data, req.body.id, req.currentUser); + await ActivitiesService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/lessons/{id}: + * /api/activities/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Lessons] + * tags: [Activities] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -211,7 +205,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 400: * description: Invalid ID supplied * 401: @@ -222,18 +216,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await LessonsService.remove(req.params.id, req.currentUser); + await ActivitiesService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/lessons/deleteByIds: + * /api/activities/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Lessons] + * tags: [Activities] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -251,7 +245,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -260,29 +254,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await LessonsService.deleteByIds(req.body.data, req.currentUser); + await ActivitiesService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/lessons: + * /api/activities: * get: * security: * - bearerAuth: [] - * tags: [Lessons] - * summary: Get all lessons - * description: Get all lessons + * tags: [Activities] + * summary: Get all activities + * description: Get all activities * responses: * 200: - * description: Lessons list successfully received + * description: Activities list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -294,14 +288,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await LessonsDBApi.findAll( + const payload = await ActivitiesDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','title','content', - 'order','duration_minutes', + const fields = ['id','subject','notes', - 'release_date', + + 'start','end', ]; const opts = { fields }; try { @@ -320,22 +314,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/lessons/count: + * /api/activities/count: * get: * security: * - bearerAuth: [] - * tags: [Lessons] - * summary: Count all lessons - * description: Count all lessons + * tags: [Activities] + * summary: Count all activities + * description: Count all activities * responses: * 200: - * description: Lessons count successfully received + * description: Activities count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -346,7 +340,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await LessonsDBApi.findAll( + const payload = await ActivitiesDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -357,22 +351,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/lessons/autocomplete: + * /api/activities/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Lessons] - * summary: Find all lessons that match search criteria - * description: Find all lessons that match search criteria + * tags: [Activities] + * summary: Find all activities that match search criteria + * description: Find all activities that match search criteria * responses: * 200: - * description: Lessons list successfully received + * description: Activities list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -382,7 +376,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await LessonsDBApi.findAllAutocomplete( + const payload = await ActivitiesDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -394,11 +388,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/lessons/{id}: + * /api/activities/{id}: * get: * security: * - bearerAuth: [] - * tags: [Lessons] + * tags: [Activities] * summary: Get selected item * description: Get selected item * parameters: @@ -414,7 +408,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Lessons" + * $ref: "#/components/schemas/Activities" * 400: * description: Invalid ID supplied * 401: @@ -425,7 +419,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await LessonsDBApi.findBy( + const payload = await ActivitiesDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/progress.js b/backend/src/routes/contacts.js similarity index 75% rename from backend/src/routes/progress.js rename to backend/src/routes/contacts.js index 211b257..478bce2 100644 --- a/backend/src/routes/progress.js +++ b/backend/src/routes/contacts.js @@ -1,8 +1,8 @@ const express = require('express'); -const ProgressService = require('../services/progress'); -const ProgressDBApi = require('../db/api/progress'); +const ContactsService = require('../services/contacts'); +const ContactsDBApi = require('../db/api/contacts'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,45 +15,54 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('progress')); +router.use(checkCrudPermissions('contacts')); /** * @swagger * components: * schemas: - * Progress: + * Contacts: * type: object * properties: - * note: + * name: * type: string - * default: note + * default: name + * email: + * type: string + * default: email + * phone: + * type: string + * default: phone + * title: + * type: string + * default: title + * company: + * type: string + * default: company + * notes: + * type: string + * default: notes - * attempts: - * type: integer - * format: int64 - * score: - * type: integer - * format: int64 */ /** * @swagger * tags: - * name: Progress - * description: The Progress managing API + * name: Contacts + * description: The Contacts managing API */ /** * @swagger -* /api/progress: +* /api/contacts: * post: * security: * - bearerAuth: [] -* tags: [Progress] +* tags: [Contacts] * summary: Add new item * description: Add new item * requestBody: @@ -65,14 +74,14 @@ router.use(checkCrudPermissions('progress')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Progress" +* $ref: "#/components/schemas/Contacts" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Progress" +* $ref: "#/components/schemas/Contacts" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -83,7 +92,7 @@ router.use(checkCrudPermissions('progress')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await ProgressService.create(req.body.data, req.currentUser, true, link.host); + await ContactsService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -94,7 +103,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Progress] + * tags: [Contacts] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -107,14 +116,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -126,18 +135,18 @@ router.post('/', wrapAsync(async (req, res) => { 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 ProgressService.bulkImport(req, res, true, link.host); + await ContactsService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/progress/{id}: + * /api/contacts/{id}: * put: * security: * - bearerAuth: [] - * tags: [Progress] + * tags: [Contacts] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -160,7 +169,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * required: * - id * responses: @@ -169,7 +178,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 400: * description: Invalid ID supplied * 401: @@ -180,18 +189,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await ProgressService.update(req.body.data, req.body.id, req.currentUser); + await ContactsService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/progress/{id}: + * /api/contacts/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Progress] + * tags: [Contacts] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -207,7 +216,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 400: * description: Invalid ID supplied * 401: @@ -218,18 +227,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await ProgressService.remove(req.params.id, req.currentUser); + await ContactsService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/progress/deleteByIds: + * /api/contacts/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Progress] + * tags: [Contacts] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -247,7 +256,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -256,29 +265,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await ProgressService.deleteByIds(req.body.data, req.currentUser); + await ContactsService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/progress: + * /api/contacts: * get: * security: * - bearerAuth: [] - * tags: [Progress] - * summary: Get all progress - * description: Get all progress + * tags: [Contacts] + * summary: Get all contacts + * description: Get all contacts * responses: * 200: - * description: Progress list successfully received + * description: Contacts list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -290,14 +299,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await ProgressDBApi.findAll( + const payload = await ContactsDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','note', - 'attempts', - 'score', - 'completed_at', + const fields = ['id','name','email','phone','title','company','notes', + + + ]; const opts = { fields }; try { @@ -316,22 +325,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/progress/count: + * /api/contacts/count: * get: * security: * - bearerAuth: [] - * tags: [Progress] - * summary: Count all progress - * description: Count all progress + * tags: [Contacts] + * summary: Count all contacts + * description: Count all contacts * responses: * 200: - * description: Progress count successfully received + * description: Contacts count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -342,7 +351,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await ProgressDBApi.findAll( + const payload = await ContactsDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -353,22 +362,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/progress/autocomplete: + * /api/contacts/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Progress] - * summary: Find all progress that match search criteria - * description: Find all progress that match search criteria + * tags: [Contacts] + * summary: Find all contacts that match search criteria + * description: Find all contacts that match search criteria * responses: * 200: - * description: Progress list successfully received + * description: Contacts list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -378,7 +387,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await ProgressDBApi.findAllAutocomplete( + const payload = await ContactsDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -390,11 +399,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/progress/{id}: + * /api/contacts/{id}: * get: * security: * - bearerAuth: [] - * tags: [Progress] + * tags: [Contacts] * summary: Get selected item * description: Get selected item * parameters: @@ -410,7 +419,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Progress" + * $ref: "#/components/schemas/Contacts" * 400: * description: Invalid ID supplied * 401: @@ -421,7 +430,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await ProgressDBApi.findBy( + const payload = await ContactsDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/deals.js b/backend/src/routes/deals.js new file mode 100644 index 0000000..dd1c175 --- /dev/null +++ b/backend/src/routes/deals.js @@ -0,0 +1,461 @@ + +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: + + * title: + * type: string + * default: title + * deal_number: + * type: string + * default: deal_number + * currency: + * type: string + * default: currency + * description: + * type: string + * default: description + + + * value: + * 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','title','deal_number','currency','description', + + 'value', + 'close_date', + ]; + 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 + */ +/** + * @swagger + * /api/deals/stats: + * get: + * security: + * - bearerAuth: [] + * tags: [Deals] + * summary: Get deals stats + * description: Get deals stats + * responses: + * 200: + * description: Deals stats successfully received + */ +router.get('/stats', wrapAsync(async (req, res) => { + const currentUser = req.currentUser; + const payload = await DealsDBApi.stats({ currentUser }); + res.status(200).send(payload); +})); + +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/courses.js b/backend/src/routes/leads.js similarity index 77% rename from backend/src/routes/courses.js rename to backend/src/routes/leads.js index 7450b9b..1d0314c 100644 --- a/backend/src/routes/courses.js +++ b/backend/src/routes/leads.js @@ -1,8 +1,8 @@ const express = require('express'); -const CoursesService = require('../services/courses'); -const CoursesDBApi = require('../db/api/courses'); +const LeadsService = require('../services/leads'); +const LeadsDBApi = require('../db/api/leads'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,29 +15,35 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('courses')); +router.use(checkCrudPermissions('leads')); /** * @swagger * components: * schemas: - * Courses: + * Leads: * type: object * properties: - * title: + * name: * type: string - * default: title - * description: + * default: name + * company: * type: string - * default: description - * language: + * default: company + * email: * type: string - * default: language + * default: email + * phone: + * type: string + * default: phone + * notes: + * type: string + * default: notes - * price: + * estimated_value: * type: integer * format: int64 @@ -48,17 +54,17 @@ router.use(checkCrudPermissions('courses')); /** * @swagger * tags: - * name: Courses - * description: The Courses managing API + * name: Leads + * description: The Leads managing API */ /** * @swagger -* /api/courses: +* /api/leads: * post: * security: * - bearerAuth: [] -* tags: [Courses] +* tags: [Leads] * summary: Add new item * description: Add new item * requestBody: @@ -70,14 +76,14 @@ router.use(checkCrudPermissions('courses')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Courses" +* $ref: "#/components/schemas/Leads" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Courses" +* $ref: "#/components/schemas/Leads" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -88,7 +94,7 @@ router.use(checkCrudPermissions('courses')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await CoursesService.create(req.body.data, req.currentUser, true, link.host); + await LeadsService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -99,7 +105,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Courses] + * tags: [Leads] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -112,14 +118,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -131,18 +137,18 @@ router.post('/', wrapAsync(async (req, res) => { 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 CoursesService.bulkImport(req, res, true, link.host); + await LeadsService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/courses/{id}: + * /api/leads/{id}: * put: * security: * - bearerAuth: [] - * tags: [Courses] + * tags: [Leads] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -165,7 +171,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * required: * - id * responses: @@ -174,7 +180,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 400: * description: Invalid ID supplied * 401: @@ -185,18 +191,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await CoursesService.update(req.body.data, req.body.id, req.currentUser); + await LeadsService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/courses/{id}: + * /api/leads/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Courses] + * tags: [Leads] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -212,7 +218,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 400: * description: Invalid ID supplied * 401: @@ -223,18 +229,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await CoursesService.remove(req.params.id, req.currentUser); + await LeadsService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/courses/deleteByIds: + * /api/leads/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Courses] + * tags: [Leads] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -252,7 +258,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -261,29 +267,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await CoursesService.deleteByIds(req.body.data, req.currentUser); + await LeadsService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/courses: + * /api/leads: * get: * security: * - bearerAuth: [] - * tags: [Courses] - * summary: Get all courses - * description: Get all courses + * tags: [Leads] + * summary: Get all leads + * description: Get all leads * responses: * 200: - * description: Courses list successfully received + * description: Leads list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -295,14 +301,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await CoursesDBApi.findAll( + const payload = await LeadsDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','title','description','language', + const fields = ['id','name','company','email','phone','notes', - 'price', - 'start_date','end_date', + 'estimated_value', + ]; const opts = { fields }; try { @@ -321,22 +327,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/courses/count: + * /api/leads/count: * get: * security: * - bearerAuth: [] - * tags: [Courses] - * summary: Count all courses - * description: Count all courses + * tags: [Leads] + * summary: Count all leads + * description: Count all leads * responses: * 200: - * description: Courses count successfully received + * description: Leads count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -347,7 +353,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await CoursesDBApi.findAll( + const payload = await LeadsDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -358,22 +364,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/courses/autocomplete: + * /api/leads/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Courses] - * summary: Find all courses that match search criteria - * description: Find all courses that match search criteria + * tags: [Leads] + * summary: Find all leads that match search criteria + * description: Find all leads that match search criteria * responses: * 200: - * description: Courses list successfully received + * description: Leads list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -383,7 +389,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await CoursesDBApi.findAllAutocomplete( + const payload = await LeadsDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -395,11 +401,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/courses/{id}: + * /api/leads/{id}: * get: * security: * - bearerAuth: [] - * tags: [Courses] + * tags: [Leads] * summary: Get selected item * description: Get selected item * parameters: @@ -415,7 +421,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Courses" + * $ref: "#/components/schemas/Leads" * 400: * description: Invalid ID supplied * 401: @@ -426,7 +432,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await CoursesDBApi.findBy( + const payload = await LeadsDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/enrollments.js b/backend/src/routes/pipeline_stages.js similarity index 75% rename from backend/src/routes/enrollments.js rename to backend/src/routes/pipeline_stages.js index 3985e18..a0a8306 100644 --- a/backend/src/routes/enrollments.js +++ b/backend/src/routes/pipeline_stages.js @@ -1,8 +1,8 @@ const express = require('express'); -const EnrollmentsService = require('../services/enrollments'); -const EnrollmentsDBApi = require('../db/api/enrollments'); +const Pipeline_stagesService = require('../services/pipeline_stages'); +const Pipeline_stagesDBApi = require('../db/api/pipeline_stages'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,46 +15,45 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('enrollments')); +router.use(checkCrudPermissions('pipeline_stages')); /** * @swagger * components: * schemas: - * Enrollments: + * Pipeline_stages: * type: object * properties: - * enrollment_label: + * title: * type: string - * default: enrollment_label + * default: title - - * progress_percent: + * order: * type: integer * format: int64 - * price_paid: + * probability: * type: integer * format: int64 - * + */ /** * @swagger * tags: - * name: Enrollments - * description: The Enrollments managing API + * name: Pipeline_stages + * description: The Pipeline_stages managing API */ /** * @swagger -* /api/enrollments: +* /api/pipeline_stages: * post: * security: * - bearerAuth: [] -* tags: [Enrollments] +* tags: [Pipeline_stages] * summary: Add new item * description: Add new item * requestBody: @@ -66,14 +65,14 @@ router.use(checkCrudPermissions('enrollments')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Enrollments" +* $ref: "#/components/schemas/Pipeline_stages" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Enrollments" +* $ref: "#/components/schemas/Pipeline_stages" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -84,7 +83,7 @@ router.use(checkCrudPermissions('enrollments')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await EnrollmentsService.create(req.body.data, req.currentUser, true, link.host); + await Pipeline_stagesService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -95,7 +94,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Enrollments] + * tags: [Pipeline_stages] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -108,14 +107,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -127,18 +126,18 @@ router.post('/', wrapAsync(async (req, res) => { 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 EnrollmentsService.bulkImport(req, res, true, link.host); + await Pipeline_stagesService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/enrollments/{id}: + * /api/pipeline_stages/{id}: * put: * security: * - bearerAuth: [] - * tags: [Enrollments] + * tags: [Pipeline_stages] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -161,7 +160,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * required: * - id * responses: @@ -170,7 +169,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 400: * description: Invalid ID supplied * 401: @@ -181,18 +180,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await EnrollmentsService.update(req.body.data, req.body.id, req.currentUser); + await Pipeline_stagesService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/enrollments/{id}: + * /api/pipeline_stages/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Enrollments] + * tags: [Pipeline_stages] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -208,7 +207,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 400: * description: Invalid ID supplied * 401: @@ -219,18 +218,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await EnrollmentsService.remove(req.params.id, req.currentUser); + await Pipeline_stagesService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/enrollments/deleteByIds: + * /api/pipeline_stages/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Enrollments] + * tags: [Pipeline_stages] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -248,7 +247,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -257,29 +256,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await EnrollmentsService.deleteByIds(req.body.data, req.currentUser); + await Pipeline_stagesService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/enrollments: + * /api/pipeline_stages: * get: * security: * - bearerAuth: [] - * tags: [Enrollments] - * summary: Get all enrollments - * description: Get all enrollments + * tags: [Pipeline_stages] + * summary: Get all pipeline_stages + * description: Get all pipeline_stages * responses: * 200: - * description: Enrollments list successfully received + * description: Pipeline_stages list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -291,14 +290,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await EnrollmentsDBApi.findAll( + const payload = await Pipeline_stagesDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','enrollment_label', + const fields = ['id','title', + 'order','probability', - 'progress_percent','price_paid', - 'enrolled_at', + ]; const opts = { fields }; try { @@ -317,22 +316,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/enrollments/count: + * /api/pipeline_stages/count: * get: * security: * - bearerAuth: [] - * tags: [Enrollments] - * summary: Count all enrollments - * description: Count all enrollments + * tags: [Pipeline_stages] + * summary: Count all pipeline_stages + * description: Count all pipeline_stages * responses: * 200: - * description: Enrollments count successfully received + * description: Pipeline_stages count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -343,7 +342,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await EnrollmentsDBApi.findAll( + const payload = await Pipeline_stagesDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -354,22 +353,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/enrollments/autocomplete: + * /api/pipeline_stages/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Enrollments] - * summary: Find all enrollments that match search criteria - * description: Find all enrollments that match search criteria + * tags: [Pipeline_stages] + * summary: Find all pipeline_stages that match search criteria + * description: Find all pipeline_stages that match search criteria * responses: * 200: - * description: Enrollments list successfully received + * description: Pipeline_stages list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -379,7 +378,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await EnrollmentsDBApi.findAllAutocomplete( + const payload = await Pipeline_stagesDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -391,11 +390,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/enrollments/{id}: + * /api/pipeline_stages/{id}: * get: * security: * - bearerAuth: [] - * tags: [Enrollments] + * tags: [Pipeline_stages] * summary: Get selected item * description: Get selected item * parameters: @@ -411,7 +410,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Enrollments" + * $ref: "#/components/schemas/Pipeline_stages" * 400: * description: Invalid ID supplied * 401: @@ -422,7 +421,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await EnrollmentsDBApi.findBy( + const payload = await Pipeline_stagesDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/sql.js b/backend/src/routes/sql.js new file mode 100644 index 0000000..b844f07 --- /dev/null +++ b/backend/src/routes/sql.js @@ -0,0 +1,61 @@ +const express = require('express'); +const db = require('../db/models'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +/** + * @swagger + * /api/sql: + * post: + * security: + * - bearerAuth: [] + * summary: Execute a SELECT-only SQL query + * description: Executes a read-only SQL query and returns rows. + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * sql: + * type: string + * required: + * - sql + * responses: + * 200: + * description: Query result + * 400: + * description: Invalid SQL + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 500: + * description: Internal server error + */ +router.post( + '/', + wrapAsync(async (req, res) => { + const { sql } = req.body; + if (typeof sql !== 'string' || !sql.trim()) { + return res.status(400).json({ error: 'SQL is required' }); + } + + const normalized = sql.trim().replace(/;+\s*$/, ''); + if (!/^select\b/i.test(normalized)) { + return res.status(400).json({ error: 'Only SELECT statements are allowed' }); + } + + if (normalized.includes(';')) { + return res.status(400).json({ error: 'Only a single SELECT statement is allowed' }); + } + + const rows = await db.sequelize.query(normalized, { + type: db.Sequelize.QueryTypes.SELECT, + }); + + return res.status(200).json({ rows }); + }), +); + +module.exports = router; diff --git a/backend/src/services/enrollments.js b/backend/src/services/activities.js similarity index 84% rename from backend/src/services/enrollments.js rename to backend/src/services/activities.js index 7dde01e..8fc3266 100644 --- a/backend/src/services/enrollments.js +++ b/backend/src/services/activities.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const EnrollmentsDBApi = require('../db/api/enrollments'); +const ActivitiesDBApi = require('../db/api/activities'); const processFile = require("../middlewares/upload"); const ValidationError = require('./notifications/errors/validation'); const csv = require('csv-parser'); @@ -11,11 +11,11 @@ const stream = require('stream'); -module.exports = class EnrollmentsService { +module.exports = class ActivitiesService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await EnrollmentsDBApi.create( + await ActivitiesDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class EnrollmentsService { .on('error', (error) => reject(error)); }) - await EnrollmentsDBApi.bulkImport(results, { + await ActivitiesDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class EnrollmentsService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let enrollments = await EnrollmentsDBApi.findBy( + let activities = await ActivitiesDBApi.findBy( {id}, {transaction}, ); - if (!enrollments) { + if (!activities) { throw new ValidationError( - 'enrollmentsNotFound', + 'activitiesNotFound', ); } - const updatedEnrollments = await EnrollmentsDBApi.update( + const updatedActivities = await ActivitiesDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class EnrollmentsService { ); await transaction.commit(); - return updatedEnrollments; + return updatedActivities; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class EnrollmentsService { const transaction = await db.sequelize.transaction(); try { - await EnrollmentsDBApi.deleteByIds(ids, { + await ActivitiesDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class EnrollmentsService { const transaction = await db.sequelize.transaction(); try { - await EnrollmentsDBApi.remove( + await ActivitiesDBApi.remove( id, { currentUser, diff --git a/backend/src/services/progress.js b/backend/src/services/contacts.js similarity index 85% rename from backend/src/services/progress.js rename to backend/src/services/contacts.js index 7b3e7bf..7189d73 100644 --- a/backend/src/services/progress.js +++ b/backend/src/services/contacts.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const ProgressDBApi = require('../db/api/progress'); +const ContactsDBApi = require('../db/api/contacts'); const processFile = require("../middlewares/upload"); const ValidationError = require('./notifications/errors/validation'); const csv = require('csv-parser'); @@ -11,11 +11,11 @@ const stream = require('stream'); -module.exports = class ProgressService { +module.exports = class ContactsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await ProgressDBApi.create( + await ContactsDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class ProgressService { .on('error', (error) => reject(error)); }) - await ProgressDBApi.bulkImport(results, { + await ContactsDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class ProgressService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let progress = await ProgressDBApi.findBy( + let contacts = await ContactsDBApi.findBy( {id}, {transaction}, ); - if (!progress) { + if (!contacts) { throw new ValidationError( - 'progressNotFound', + 'contactsNotFound', ); } - const updatedProgress = await ProgressDBApi.update( + const updatedContacts = await ContactsDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class ProgressService { ); await transaction.commit(); - return updatedProgress; + return updatedContacts; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class ProgressService { const transaction = await db.sequelize.transaction(); try { - await ProgressDBApi.deleteByIds(ids, { + await ContactsDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class ProgressService { const transaction = await db.sequelize.transaction(); try { - await ProgressDBApi.remove( + await ContactsDBApi.remove( id, { currentUser, diff --git a/backend/src/services/courses.js b/backend/src/services/deals.js similarity index 85% rename from backend/src/services/courses.js rename to backend/src/services/deals.js index a63b30c..a611dab 100644 --- a/backend/src/services/courses.js +++ b/backend/src/services/deals.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const CoursesDBApi = require('../db/api/courses'); +const DealsDBApi = require('../db/api/deals'); const processFile = require("../middlewares/upload"); const ValidationError = require('./notifications/errors/validation'); const csv = require('csv-parser'); @@ -11,11 +11,11 @@ const stream = require('stream'); -module.exports = class CoursesService { +module.exports = class DealsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await CoursesDBApi.create( + await DealsDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class CoursesService { .on('error', (error) => reject(error)); }) - await CoursesDBApi.bulkImport(results, { + await DealsDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class CoursesService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let courses = await CoursesDBApi.findBy( + let deals = await DealsDBApi.findBy( {id}, {transaction}, ); - if (!courses) { + if (!deals) { throw new ValidationError( - 'coursesNotFound', + 'dealsNotFound', ); } - const updatedCourses = await CoursesDBApi.update( + const updatedDeals = await DealsDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class CoursesService { ); await transaction.commit(); - return updatedCourses; + return updatedDeals; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class CoursesService { const transaction = await db.sequelize.transaction(); try { - await CoursesDBApi.deleteByIds(ids, { + await DealsDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class CoursesService { const transaction = await db.sequelize.transaction(); try { - await CoursesDBApi.remove( + await DealsDBApi.remove( id, { currentUser, diff --git a/backend/src/services/lessons.js b/backend/src/services/leads.js similarity index 85% rename from backend/src/services/lessons.js rename to backend/src/services/leads.js index 0740b3a..4aff6d5 100644 --- a/backend/src/services/lessons.js +++ b/backend/src/services/leads.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const LessonsDBApi = require('../db/api/lessons'); +const LeadsDBApi = require('../db/api/leads'); const processFile = require("../middlewares/upload"); const ValidationError = require('./notifications/errors/validation'); const csv = require('csv-parser'); @@ -11,11 +11,11 @@ const stream = require('stream'); -module.exports = class LessonsService { +module.exports = class LeadsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await LessonsDBApi.create( + await LeadsDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class LessonsService { .on('error', (error) => reject(error)); }) - await LessonsDBApi.bulkImport(results, { + await LeadsDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class LessonsService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let lessons = await LessonsDBApi.findBy( + let leads = await LeadsDBApi.findBy( {id}, {transaction}, ); - if (!lessons) { + if (!leads) { throw new ValidationError( - 'lessonsNotFound', + 'leadsNotFound', ); } - const updatedLessons = await LessonsDBApi.update( + const updatedLeads = await LeadsDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class LessonsService { ); await transaction.commit(); - return updatedLessons; + return updatedLeads; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class LessonsService { const transaction = await db.sequelize.transaction(); try { - await LessonsDBApi.deleteByIds(ids, { + await LeadsDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class LessonsService { const transaction = await db.sequelize.transaction(); try { - await LessonsDBApi.remove( + await LeadsDBApi.remove( id, { currentUser, diff --git a/backend/src/services/notifications/list.js b/backend/src/services/notifications/list.js index 4a3b97d..17242b0 100644 --- a/backend/src/services/notifications/list.js +++ b/backend/src/services/notifications/list.js @@ -1,6 +1,6 @@ const errors = { app: { - title: 'Instructor-Student LMS', + title: 'Sales Pipeline CRM', }, auth: { diff --git a/backend/src/services/pipeline_stages.js b/backend/src/services/pipeline_stages.js new file mode 100644 index 0000000..762b458 --- /dev/null +++ b/backend/src/services/pipeline_stages.js @@ -0,0 +1,138 @@ +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 03603ec..06d8df6 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -66,14 +66,67 @@ module.exports = class SearchService { - "courses": [ + "pipeline_stages": [ "title", + ], + + + + + + + "leads": [ + + "name", + + "company", + + "email", + + "phone", + + "notes", + + ], + + + + + + + "contacts": [ + + "name", + + "email", + + "phone", + + "title", + + "company", + + "notes", + + ], + + + + + + + "deals": [ + + "title", + + "deal_number", + + "currency", + "description", - "language", - ], @@ -81,33 +134,11 @@ module.exports = class SearchService { - "lessons": [ + "activities": [ - "title", + "subject", - "content", - - ], - - - - - - - "enrollments": [ - - "enrollment_label", - - ], - - - - - - - "progress": [ - - "note", + "notes", ], @@ -124,21 +155,11 @@ module.exports = class SearchService { - "courses": [ - - "price", - - ], - - - - - - "lessons": [ + "pipeline_stages": [ "order", - "duration_minutes", + "probability", ], @@ -146,11 +167,9 @@ module.exports = class SearchService { - "enrollments": [ + "leads": [ - "progress_percent", - - "price_paid", + "estimated_value", ], @@ -158,15 +177,21 @@ module.exports = class SearchService { - "progress": [ + + + + + "deals": [ - "score", - - "attempts", + "value", ], + + + + }; let allFoundRecords = []; diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 35b95ea..6a8e216 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -25,7 +25,7 @@ services: - ./data/db:/var/lib/postgresql/data environment: - POSTGRES_HOST_AUTH_METHOD=trust - - POSTGRES_DB=db_instructor_student_lms + - POSTGRES_DB=db_sales_pipeline_crm ports: - "5432:5432" logging: diff --git a/frontend/README.md b/frontend/README.md index 0faaa05..9b0b60d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,4 @@ -# Instructor-Student LMS +# Sales Pipeline CRM ## This project was generated by Flatlogic Platform. ## Install diff --git a/frontend/src/components/Courses/CardCourses.tsx b/frontend/src/components/Activities/CardActivities.tsx similarity index 70% rename from frontend/src/components/Courses/CardCourses.tsx rename to frontend/src/components/Activities/CardActivities.tsx index 9579da1..902d53d 100644 --- a/frontend/src/components/Courses/CardCourses.tsx +++ b/frontend/src/components/Activities/CardActivities.tsx @@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - courses: any[]; + activities: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardCourses = ({ - courses, +const CardActivities = ({ + activities, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardCourses = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSES') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACTIVITIES') return ( @@ -47,7 +47,7 @@ const CardCourses = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && courses.map((item, index) => ( + {!loading && activities.map((item, index) => (
  • -
    +
    - - -

    {item.title}

    + + {item.subject} -
    +
    -
    Title
    +
    Subject
    - { item.title } + { item.subject }
    @@ -99,10 +90,10 @@ const CardCourses = ({
    -
    Description
    +
    ActivityType
    - { item.description } + { item.activity_type }
    @@ -111,10 +102,10 @@ const CardCourses = ({
    -
    Instructor
    +
    Start
    - { dataFormatter.usersOneListFormatter(item.instructor) } + { dataFormatter.dateTimeFormatter(item.start) }
    @@ -123,10 +114,10 @@ const CardCourses = ({
    -
    Category
    +
    End
    - { item.category } + { dataFormatter.dateTimeFormatter(item.end) }
    @@ -135,10 +126,10 @@ const CardCourses = ({
    -
    Level
    +
    Completed
    - { item.level } + { dataFormatter.booleanFormatter(item.completed) }
    @@ -147,14 +138,10 @@ const CardCourses = ({
    -
    Thumbnail
    +
    Owner
    -
    - +
    + { dataFormatter.usersOneListFormatter(item.owner) }
    @@ -163,10 +150,10 @@ const CardCourses = ({
    -
    Published
    +
    RelatedDeal
    - { dataFormatter.booleanFormatter(item.published) } + { dataFormatter.dealsOneListFormatter(item.related_deal) }
    @@ -175,10 +162,10 @@ const CardCourses = ({
    -
    StartDate
    +
    RelatedLead
    - { dataFormatter.dateTimeFormatter(item.start_date) } + { dataFormatter.leadsOneListFormatter(item.related_lead) }
    @@ -187,34 +174,10 @@ const CardCourses = ({
    -
    EndDate
    +
    Notes
    - { dataFormatter.dateTimeFormatter(item.end_date) } -
    -
    -
    - - - - -
    -
    Price
    -
    -
    - { item.price } -
    -
    -
    - - - - -
    -
    Language
    -
    -
    - { item.language } + { item.notes }
    @@ -224,7 +187,7 @@ const CardCourses = ({
  • ))} - {!loading && courses.length === 0 && ( + {!loading && activities.length === 0 && (

    No data to display

    @@ -241,4 +204,4 @@ const CardCourses = ({ ); }; -export default CardCourses; +export default CardActivities; diff --git a/frontend/src/components/Lessons/ListLessons.tsx b/frontend/src/components/Activities/ListActivities.tsx similarity index 73% rename from frontend/src/components/Lessons/ListLessons.tsx rename to frontend/src/components/Activities/ListActivities.tsx index ccd4f95..5c9b95b 100644 --- a/frontend/src/components/Lessons/ListLessons.tsx +++ b/frontend/src/components/Activities/ListActivities.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - lessons: any[]; + activities: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListLessons = ({ lessons, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListActivities = ({ activities, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LESSONS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ACTIVITIES') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListLessons = ({ lessons, loading, onDelete, currentPage, numPages, onPage <>
    {loading && } - {!loading && lessons.map((item) => ( + {!loading && activities.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -48,86 +48,72 @@ const ListLessons = ({ lessons, loading, onDelete, currentPage, numPages, onPage
    -

    Title

    -

    { item.title }

    +

    Subject

    +

    { item.subject }

    -

    Content

    -

    { item.content }

    +

    ActivityType

    +

    { item.activity_type }

    -

    Course

    -

    { dataFormatter.coursesOneListFormatter(item.course) }

    +

    Start

    +

    { dataFormatter.dateTimeFormatter(item.start) }

    -

    Order

    -

    { item.order }

    +

    End

    +

    { dataFormatter.dateTimeFormatter(item.end) }

    -

    Duration(minutes)

    -

    { item.duration_minutes }

    +

    Completed

    +

    { dataFormatter.booleanFormatter(item.completed) }

    -

    VideoFiles

    - {dataFormatter.filesFormatter(item.video_files).map(link => ( - - ))} +

    Owner

    +

    { dataFormatter.usersOneListFormatter(item.owner) }

    -

    Resources

    - {dataFormatter.filesFormatter(item.resources).map(link => ( - - ))} +

    RelatedDeal

    +

    { dataFormatter.dealsOneListFormatter(item.related_deal) }

    -

    ReleaseDate

    -

    { dataFormatter.dateTimeFormatter(item.release_date) }

    +

    RelatedLead

    +

    { dataFormatter.leadsOneListFormatter(item.related_lead) }

    -

    Status

    -

    { item.status }

    +

    Notes

    +

    { item.notes }

    @@ -136,8 +122,8 @@ const ListLessons = ({ lessons, loading, onDelete, currentPage, numPages, onPage
    ))} - {!loading && lessons.length === 0 && ( + {!loading && activities.length === 0 && (

    No data to display

    @@ -163,4 +149,4 @@ const ListLessons = ({ lessons, loading, onDelete, currentPage, numPages, onPage ) }; -export default ListLessons \ No newline at end of file +export default ListActivities \ No newline at end of file diff --git a/frontend/src/components/Courses/TableCourses.tsx b/frontend/src/components/Activities/TableActivities.tsx similarity index 94% rename from frontend/src/components/Courses/TableCourses.tsx rename to frontend/src/components/Activities/TableActivities.tsx index 169c4b5..46d0c56 100644 --- a/frontend/src/components/Courses/TableCourses.tsx +++ b/frontend/src/components/Activities/TableActivities.tsx @@ -4,7 +4,7 @@ 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/courses/coursesSlice' +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"; @@ -12,7 +12,7 @@ import { DataGrid, GridColDef, } from '@mui/x-data-grid'; -import {loadColumns} from "./configureCoursesCols"; +import {loadColumns} from "./configureActivitiesCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; @@ -24,7 +24,7 @@ import { SlotInfo } from 'react-big-calendar'; const perPage = 100 -const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleActivities = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -43,7 +43,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) }, ]); - const { courses, loading, count, notify: coursesNotify, refetch } = useAppSelector((state) => state.courses) + 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); @@ -63,10 +63,10 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) }; useEffect(() => { - if (coursesNotify.showNotification) { - notify(coursesNotify.typeNotification, coursesNotify.textNotification); + if (activitiesNotify.showNotification) { + notify(activitiesNotify.typeNotification, activitiesNotify.textNotification); } - }, [coursesNotify.showNotification]); + }, [activitiesNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -93,7 +93,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) const handleCreateEventAction = ({ start, end }: SlotInfo) => { router.push( - `/courses/courses-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, + `/activities/activities-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, ); }; @@ -186,7 +186,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) loadColumns( handleDeleteModalAction, - `courses`, + `activities`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -224,7 +224,7 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={courses ?? []} + rows={activities ?? []} columns={columns} initialState={{ pagination: { @@ -451,18 +451,18 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) {!showGrid && ( { loadData(0,`&calendarStart=${range.start}&calendarEnd=${range.end}`); }} - entityName={'courses'} + entityName={'activities'} /> )} @@ -486,4 +486,4 @@ const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) ) } -export default TableSampleCourses +export default TableSampleActivities diff --git a/frontend/src/components/Lessons/configureLessonsCols.tsx b/frontend/src/components/Activities/configureActivitiesCols.tsx similarity index 70% rename from frontend/src/components/Lessons/configureLessonsCols.tsx rename to frontend/src/components/Activities/configureActivitiesCols.tsx index 0519cfd..e6dd564 100644 --- a/frontend/src/components/Lessons/configureLessonsCols.tsx +++ b/frontend/src/components/Activities/configureActivitiesCols.tsx @@ -37,13 +37,13 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_LESSONS') + const hasUpdatePermission = hasPermission(user, 'UPDATE_ACTIVITIES') return [ { - field: 'title', - headerName: 'Title', + field: 'subject', + headerName: 'Subject', flex: 1, minWidth: 120, filterable: false, @@ -57,8 +57,8 @@ export const loadColumns = async ( }, { - field: 'content', - headerName: 'Content', + field: 'activity_type', + headerName: 'ActivityType', flex: 1, minWidth: 120, filterable: false, @@ -72,8 +72,60 @@ export const loadColumns = async ( }, { - field: 'course', - headerName: 'Course', + field: 'start', + headerName: 'Start', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.start), + + }, + + { + field: 'end', + headerName: 'End', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.end), + + }, + + { + field: 'completed', + headerName: 'Completed', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'boolean', + + }, + + { + field: 'owner', + headerName: 'Owner', flex: 1, minWidth: 120, filterable: false, @@ -87,15 +139,15 @@ export const loadColumns = async ( type: 'singleSelect', getOptionValue: (value: any) => value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('courses'), + valueOptions: await callOptionsApi('users'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, { - field: 'order', - headerName: 'Order', + field: 'related_deal', + headerName: 'RelatedDeal', flex: 1, minWidth: 120, filterable: false, @@ -105,99 +157,41 @@ export const loadColumns = async ( editable: hasUpdatePermission, - type: 'number', - - }, - - { - field: 'duration_minutes', - headerName: 'Duration(minutes)', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'video_files', - headerName: 'VideoFiles', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, sortable: false, - renderCell: (params: GridValueGetterParams) => ( - <> - {dataFormatter.filesFormatter(params.row.video_files).map(link => ( - - ))} - - ), - - }, - - { - field: 'resources', - headerName: 'Resources', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - <> - {dataFormatter.filesFormatter(params.row.resources).map(link => ( - - ))} - - ), - - }, - - { - field: 'release_date', - headerName: 'ReleaseDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('deals'), valueGetter: (params: GridValueGetterParams) => - new Date(params.row.release_date), + params?.value?.id ?? params?.value, }, { - field: 'status', - headerName: 'Status', + field: 'related_lead', + headerName: 'RelatedLead', + 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: 'notes', + headerName: 'Notes', flex: 1, minWidth: 120, filterable: false, @@ -223,8 +217,8 @@ export const loadColumns = async (
    - Instructor-Student LMS + Sales Pipeline CRM
    diff --git a/frontend/src/components/Progress/CardProgress.tsx b/frontend/src/components/Contacts/CardContacts.tsx similarity index 84% rename from frontend/src/components/Progress/CardProgress.tsx rename to frontend/src/components/Contacts/CardContacts.tsx index 635e2bb..0d4b87c 100644 --- a/frontend/src/components/Progress/CardProgress.tsx +++ b/frontend/src/components/Contacts/CardContacts.tsx @@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - progress: any[]; + contacts: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardProgress = ({ - progress, +const CardContacts = ({ + contacts, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardProgress = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROGRESS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CONTACTS') return ( @@ -47,7 +47,7 @@ const CardProgress = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && progress.map((item, index) => ( + {!loading && contacts.map((item, index) => (
  • - - {item.note} + + {item.name} @@ -66,8 +66,8 @@ const CardProgress = ({ -
    Note
    +
    Name
    - { item.note } + { item.name }
  • @@ -90,10 +90,10 @@ const CardProgress = ({
    -
    Enrollment
    +
    Email
    - { dataFormatter.enrollmentsOneListFormatter(item.enrollment) } + { item.email }
    @@ -102,10 +102,10 @@ const CardProgress = ({
    -
    Lesson
    +
    Phone
    - { dataFormatter.lessonsOneListFormatter(item.lesson) } + { item.phone }
    @@ -114,10 +114,10 @@ const CardProgress = ({
    -
    Completed
    +
    JobTitle
    - { dataFormatter.booleanFormatter(item.completed) } + { item.title }
    @@ -126,10 +126,10 @@ const CardProgress = ({
    -
    CompletedAt
    +
    Company
    - { dataFormatter.dateTimeFormatter(item.completed_at) } + { item.company }
    @@ -138,10 +138,10 @@ const CardProgress = ({
    -
    Score
    +
    Owner
    - { item.score } + { dataFormatter.usersOneListFormatter(item.owner) }
    @@ -150,10 +150,10 @@ const CardProgress = ({
    -
    Attempts
    +
    Notes
    - { item.attempts } + { item.notes }
    @@ -163,7 +163,7 @@ const CardProgress = ({ ))} - {!loading && progress.length === 0 && ( + {!loading && contacts.length === 0 && (

    No data to display

    @@ -180,4 +180,4 @@ const CardProgress = ({ ); }; -export default CardProgress; +export default CardContacts; diff --git a/frontend/src/components/Progress/ListProgress.tsx b/frontend/src/components/Contacts/ListContacts.tsx similarity index 84% rename from frontend/src/components/Progress/ListProgress.tsx rename to frontend/src/components/Contacts/ListContacts.tsx index ec0839d..8b0d51b 100644 --- a/frontend/src/components/Progress/ListProgress.tsx +++ b/frontend/src/components/Contacts/ListContacts.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - progress: any[]; + contacts: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListProgress = ({ progress, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListContacts = ({ contacts, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROGRESS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CONTACTS') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListProgress = ({ progress, loading, onDelete, currentPage, numPages, onPa <>
    {loading && } - {!loading && progress.map((item) => ( + {!loading && contacts.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -48,56 +48,56 @@ const ListProgress = ({ progress, loading, onDelete, currentPage, numPages, onPa
    -

    Note

    -

    { item.note }

    +

    Name

    +

    { item.name }

    -

    Enrollment

    -

    { dataFormatter.enrollmentsOneListFormatter(item.enrollment) }

    +

    Email

    +

    { item.email }

    -

    Lesson

    -

    { dataFormatter.lessonsOneListFormatter(item.lesson) }

    +

    Phone

    +

    { item.phone }

    -

    Completed

    -

    { dataFormatter.booleanFormatter(item.completed) }

    +

    JobTitle

    +

    { item.title }

    -

    CompletedAt

    -

    { dataFormatter.dateTimeFormatter(item.completed_at) }

    +

    Company

    +

    { item.company }

    -

    Score

    -

    { item.score }

    +

    Owner

    +

    { dataFormatter.usersOneListFormatter(item.owner) }

    -

    Attempts

    -

    { item.attempts }

    +

    Notes

    +

    { item.notes }

    @@ -106,8 +106,8 @@ const ListProgress = ({ progress, loading, onDelete, currentPage, numPages, onPa
    ))} - {!loading && progress.length === 0 && ( + {!loading && contacts.length === 0 && (

    No data to display

    @@ -133,4 +133,4 @@ const ListProgress = ({ progress, loading, onDelete, currentPage, numPages, onPa ) }; -export default ListProgress \ No newline at end of file +export default ListContacts \ No newline at end of file diff --git a/frontend/src/components/Progress/TableProgress.tsx b/frontend/src/components/Contacts/TableContacts.tsx similarity index 96% rename from frontend/src/components/Progress/TableProgress.tsx rename to frontend/src/components/Contacts/TableContacts.tsx index d568281..63caf1f 100644 --- a/frontend/src/components/Progress/TableProgress.tsx +++ b/frontend/src/components/Contacts/TableContacts.tsx @@ -4,7 +4,7 @@ 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/progress/progressSlice' +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"; @@ -12,18 +12,18 @@ import { DataGrid, GridColDef, } from '@mui/x-data-grid'; -import {loadColumns} from "./configureProgressCols"; +import {loadColumns} from "./configureContactsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; -import ListProgress from './ListProgress'; +import ListContacts from './ListContacts'; const perPage = 10 -const TableSampleProgress = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleContacts = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -42,7 +42,7 @@ const TableSampleProgress = ({ filterItems, setFilterItems, filters, showGrid }) }, ]); - const { progress, loading, count, notify: progressNotify, refetch } = useAppSelector((state) => state.progress) + 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); @@ -62,10 +62,10 @@ const TableSampleProgress = ({ filterItems, setFilterItems, filters, showGrid }) }; useEffect(() => { - if (progressNotify.showNotification) { - notify(progressNotify.typeNotification, progressNotify.textNotification); + if (contactsNotify.showNotification) { + notify(contactsNotify.typeNotification, contactsNotify.textNotification); } - }, [progressNotify.showNotification]); + }, [contactsNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -179,7 +179,7 @@ const TableSampleProgress = ({ filterItems, setFilterItems, filters, showGrid }) loadColumns( handleDeleteModalAction, - `progress`, + `contacts`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -217,7 +217,7 @@ const TableSampleProgress = ({ filterItems, setFilterItems, filters, showGrid }) sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={progress ?? []} + rows={contacts ?? []} columns={columns} initialState={{ pagination: { @@ -442,9 +442,9 @@ const TableSampleProgress = ({ filterItems, setFilterItems, filters, showGrid }) - {progress && Array.isArray(progress) && !showGrid && ( - value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('enrollments'), + valueOptions: await callOptionsApi('users'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, { - field: 'lesson', - headerName: 'Lesson', + field: 'notes', + headerName: 'Notes', flex: 1, minWidth: 120, filterable: false, @@ -90,80 +150,7 @@ export const loadColumns = async ( editable: hasUpdatePermission, - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('lessons'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'completed', - headerName: 'Completed', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - 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: 'score', - headerName: 'Score', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'attempts', - headerName: 'Attempts', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - }, { @@ -179,8 +166,8 @@ export const loadColumns = async ( void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardLessons = ({ - lessons, +const CardDeals = ({ + deals, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardLessons = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LESSONS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_DEALS') return ( @@ -47,7 +47,7 @@ const CardLessons = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && lessons.map((item, index) => ( + {!loading && deals.map((item, index) => (
  • - + {item.title} @@ -66,8 +66,8 @@ const CardLessons = ({ -
    Content
    +
    DealNumber
    - { item.content } + { item.deal_number }
  • @@ -102,10 +102,10 @@ const CardLessons = ({
    -
    Course
    +
    Value
    - { dataFormatter.coursesOneListFormatter(item.course) } + { item.value }
    @@ -114,10 +114,10 @@ const CardLessons = ({
    -
    Order
    +
    Currency
    - { item.order } + { item.currency }
    @@ -126,60 +126,10 @@ const CardLessons = ({
    -
    Duration(minutes)
    +
    Stage
    - { item.duration_minutes } -
    -
    -
    - - - - -
    -
    VideoFiles
    -
    -
    - {dataFormatter.filesFormatter(item.video_files).map(link => ( - - ))} -
    -
    -
    - - - - -
    -
    Resources
    -
    -
    - {dataFormatter.filesFormatter(item.resources).map(link => ( - - ))} -
    -
    -
    - - - - -
    -
    ReleaseDate
    -
    -
    - { dataFormatter.dateTimeFormatter(item.release_date) } + { dataFormatter.pipeline_stagesOneListFormatter(item.stage) }
    @@ -198,10 +148,58 @@ const CardLessons = ({ + +
    +
    CloseDate
    +
    +
    + { dataFormatter.dateTimeFormatter(item.close_date) } +
    +
    +
    + + + + +
    +
    Owner
    +
    +
    + { dataFormatter.usersOneListFormatter(item.owner) } +
    +
    +
    + + + + +
    +
    PrimaryContact
    +
    +
    + { dataFormatter.contactsOneListFormatter(item.primary_contact) } +
    +
    +
    + + + + +
    +
    Description
    +
    +
    + { item.description } +
    +
    +
    + + + ))} - {!loading && lessons.length === 0 && ( + {!loading && deals.length === 0 && (

    No data to display

    @@ -218,4 +216,4 @@ const CardLessons = ({ ); }; -export default CardLessons; +export default CardDeals; diff --git a/frontend/src/components/Courses/ListCourses.tsx b/frontend/src/components/Deals/ListDeals.tsx similarity index 75% rename from frontend/src/components/Courses/ListCourses.tsx rename to frontend/src/components/Deals/ListDeals.tsx index 47c6233..2d47868 100644 --- a/frontend/src/components/Courses/ListCourses.tsx +++ b/frontend/src/components/Deals/ListDeals.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - courses: any[]; + deals: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListCourses = ({ courses, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListDeals = ({ deals, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSES') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_DEALS') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,20 +34,13 @@ const ListCourses = ({ courses, loading, onDelete, currentPage, numPages, onPage <>
    {loading && } - {!loading && courses.map((item) => ( + {!loading && deals.map((item) => (
    - - dark:divide-dark-700 overflow-x-auto' } @@ -62,6 +55,70 @@ const ListCourses = ({ courses, loading, onDelete, currentPage, numPages, onPage +
    +

    DealNumber

    +

    { item.deal_number }

    +
    + + + + +
    +

    Value

    +

    { item.value }

    +
    + + + + +
    +

    Currency

    +

    { item.currency }

    +
    + + + + +
    +

    Stage

    +

    { dataFormatter.pipeline_stagesOneListFormatter(item.stage) }

    +
    + + + + +
    +

    Status

    +

    { item.status }

    +
    + + + + +
    +

    CloseDate

    +

    { dataFormatter.dateTimeFormatter(item.close_date) }

    +
    + + + + +
    +

    Owner

    +

    { dataFormatter.usersOneListFormatter(item.owner) }

    +
    + + + + +
    +

    PrimaryContact

    +

    { dataFormatter.contactsOneListFormatter(item.primary_contact) }

    +
    + + + +

    Description

    { item.description }

    @@ -69,88 +126,12 @@ const ListCourses = ({ courses, loading, onDelete, currentPage, numPages, onPage - -
    -

    Instructor

    -

    { dataFormatter.usersOneListFormatter(item.instructor) }

    -
    - - - - -
    -

    Category

    -

    { item.category }

    -
    - - - - -
    -

    Level

    -

    { item.level }

    -
    - - - - -
    -

    Thumbnail

    - -
    - - - - -
    -

    Published

    -

    { dataFormatter.booleanFormatter(item.published) }

    -
    - - - - -
    -

    StartDate

    -

    { dataFormatter.dateTimeFormatter(item.start_date) }

    -
    - - - - -
    -

    EndDate

    -

    { dataFormatter.dateTimeFormatter(item.end_date) }

    -
    - - - - -
    -

    Price

    -

    { item.price }

    -
    - - - - -
    -

    Language

    -

    { item.language }

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

    No data to display

    @@ -176,4 +157,4 @@ const ListCourses = ({ courses, loading, onDelete, currentPage, numPages, onPage ) }; -export default ListCourses \ No newline at end of file +export default ListDeals \ No newline at end of file diff --git a/frontend/src/components/Lessons/TableLessons.tsx b/frontend/src/components/Deals/TableDeals.tsx similarity index 95% rename from frontend/src/components/Lessons/TableLessons.tsx rename to frontend/src/components/Deals/TableDeals.tsx index 908fe3d..ab08d4b 100644 --- a/frontend/src/components/Lessons/TableLessons.tsx +++ b/frontend/src/components/Deals/TableDeals.tsx @@ -4,7 +4,7 @@ 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/lessons/lessonsSlice' +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"; @@ -12,7 +12,7 @@ import { DataGrid, GridColDef, } from '@mui/x-data-grid'; -import {loadColumns} from "./configureLessonsCols"; +import {loadColumns} from "./configureDealsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; @@ -24,7 +24,7 @@ import axios from 'axios'; const perPage = 10 -const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleDeals = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -46,7 +46,7 @@ const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) const [kanbanColumns, setKanbanColumns] = useState | null>(null); const [kanbanFilters, setKanbanFilters] = useState(''); - const { lessons, loading, count, notify: lessonsNotify, refetch } = useAppSelector((state) => state.lessons) + 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); @@ -66,10 +66,10 @@ const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) }; useEffect(() => { - if (lessonsNotify.showNotification) { - notify(lessonsNotify.typeNotification, lessonsNotify.textNotification); + if (dealsNotify.showNotification) { + notify(dealsNotify.typeNotification, dealsNotify.textNotification); } - }, [lessonsNotify.showNotification]); + }, [dealsNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -94,17 +94,18 @@ const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) useEffect(() => { - - setKanbanColumns([ - - { id: "draft", label: "draft" }, - - { id: "published", label: "published" }, - - { id: "archived", label: "archived" }, - - ]); + + + + axios.get('/pipeline_stages/autocomplete?limit=100') + .then((res) => { + setKanbanColumns(res.data); + }) + .catch((err) => { + console.error('Error fetching kanban columns:', err); + }); + }, []); @@ -206,7 +207,7 @@ const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) loadColumns( handleDeleteModalAction, - `lessons`, + `deals`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -244,7 +245,7 @@ const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={lessons ?? []} + rows={deals ?? []} columns={columns} initialState={{ pagination: { @@ -472,9 +473,9 @@ const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) {!showGrid && kanbanColumns && ( value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('pipeline_stages'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + + }, + + { + field: 'status', + headerName: 'Status', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'close_date', + headerName: 'CloseDate', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.close_date), + + }, + + { + field: 'owner', + headerName: 'Owner', flex: 1, minWidth: 120, filterable: false, @@ -94,8 +180,8 @@ export const loadColumns = async ( }, { - field: 'category', - headerName: 'Category', + field: 'primary_contact', + headerName: 'PrimaryContact', flex: 1, minWidth: 120, filterable: false, @@ -105,116 +191,19 @@ export const loadColumns = async ( editable: hasUpdatePermission, - - }, - - { - field: 'level', - headerName: 'Level', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'thumbnail', - headerName: 'Thumbnail', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, sortable: false, - renderCell: (params: GridValueGetterParams) => ( - - ), - - }, - - { - field: 'published', - headerName: 'Published', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'start_date', - headerName: 'StartDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('contacts'), valueGetter: (params: GridValueGetterParams) => - new Date(params.row.start_date), + params?.value?.id ?? params?.value, }, { - field: 'end_date', - headerName: 'EndDate', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.end_date), - - }, - - { - field: 'price', - headerName: 'Price', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'number', - - }, - - { - field: 'language', - headerName: 'Language', + field: 'description', + headerName: 'Description', flex: 1, minWidth: 120, filterable: false, @@ -240,8 +229,8 @@ export const loadColumns = async ( void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardEnrollments = ({ - enrollments, +const CardLeads = ({ + leads, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardEnrollments = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ENROLLMENTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LEADS') return ( @@ -47,7 +47,7 @@ const CardEnrollments = ({ role='list' className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > - {!loading && enrollments.map((item, index) => ( + {!loading && leads.map((item, index) => (
  • - - {item.enrollment_label} + + {item.name} @@ -66,8 +66,8 @@ const CardEnrollments = ({ -
    EnrollmentLabel
    +
    Name
    - { item.enrollment_label } + { item.name }
  • @@ -90,10 +90,10 @@ const CardEnrollments = ({
    -
    Student
    +
    Company
    - { dataFormatter.usersOneListFormatter(item.student) } + { item.company }
    @@ -102,10 +102,10 @@ const CardEnrollments = ({
    -
    Course
    +
    Email
    - { dataFormatter.coursesOneListFormatter(item.course) } + { item.email }
    @@ -114,10 +114,22 @@ const CardEnrollments = ({
    -
    EnrolledAt
    +
    Phone
    - { dataFormatter.dateTimeFormatter(item.enrolled_at) } + { item.phone } +
    +
    +
    + + + + +
    +
    Source
    +
    +
    + { item.source }
    @@ -138,10 +150,10 @@ const CardEnrollments = ({
    -
    ProgressPercent
    +
    Owner
    - { item.progress_percent } + { dataFormatter.usersOneListFormatter(item.owner) }
    @@ -150,10 +162,22 @@ const CardEnrollments = ({
    -
    PricePaid
    +
    EstimatedValue
    - { item.price_paid } + { item.estimated_value } +
    +
    +
    + + + + +
    +
    Notes
    +
    +
    + { item.notes }
    @@ -163,7 +187,7 @@ const CardEnrollments = ({ ))} - {!loading && enrollments.length === 0 && ( + {!loading && leads.length === 0 && (

    No data to display

    @@ -180,4 +204,4 @@ const CardEnrollments = ({ ); }; -export default CardEnrollments; +export default CardLeads; diff --git a/frontend/src/components/Enrollments/ListEnrollments.tsx b/frontend/src/components/Leads/ListLeads.tsx similarity index 76% rename from frontend/src/components/Enrollments/ListEnrollments.tsx rename to frontend/src/components/Leads/ListLeads.tsx index 69689ba..3178808 100644 --- a/frontend/src/components/Enrollments/ListEnrollments.tsx +++ b/frontend/src/components/Leads/ListLeads.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - enrollments: any[]; + leads: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListEnrollments = ({ enrollments, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListLeads = ({ leads, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ENROLLMENTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LEADS') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListEnrollments = ({ enrollments, loading, onDelete, currentPage, numPages <>
    {loading && } - {!loading && enrollments.map((item) => ( + {!loading && leads.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -48,32 +48,40 @@ const ListEnrollments = ({ enrollments, loading, onDelete, currentPage, numPages
    -

    EnrollmentLabel

    -

    { item.enrollment_label }

    +

    Name

    +

    { item.name }

    -

    Student

    -

    { dataFormatter.usersOneListFormatter(item.student) }

    +

    Company

    +

    { item.company }

    -

    Course

    -

    { dataFormatter.coursesOneListFormatter(item.course) }

    +

    Email

    +

    { item.email }

    -

    EnrolledAt

    -

    { dataFormatter.dateTimeFormatter(item.enrolled_at) }

    +

    Phone

    +

    { item.phone }

    +
    + + + + +
    +

    Source

    +

    { item.source }

    @@ -88,16 +96,24 @@ const ListEnrollments = ({ enrollments, loading, onDelete, currentPage, numPages
    -

    ProgressPercent

    -

    { item.progress_percent }

    +

    Owner

    +

    { dataFormatter.usersOneListFormatter(item.owner) }

    -

    PricePaid

    -

    { item.price_paid }

    +

    EstimatedValue

    +

    { item.estimated_value }

    +
    + + + + +
    +

    Notes

    +

    { item.notes }

    @@ -106,8 +122,8 @@ const ListEnrollments = ({ enrollments, loading, onDelete, currentPage, numPages
    ))} - {!loading && enrollments.length === 0 && ( + {!loading && leads.length === 0 && (

    No data to display

    @@ -133,4 +149,4 @@ const ListEnrollments = ({ enrollments, loading, onDelete, currentPage, numPages ) }; -export default ListEnrollments \ No newline at end of file +export default ListLeads \ No newline at end of file diff --git a/frontend/src/components/Enrollments/TableEnrollments.tsx b/frontend/src/components/Leads/TableLeads.tsx similarity index 96% rename from frontend/src/components/Enrollments/TableEnrollments.tsx rename to frontend/src/components/Leads/TableLeads.tsx index f585542..de522f1 100644 --- a/frontend/src/components/Enrollments/TableEnrollments.tsx +++ b/frontend/src/components/Leads/TableLeads.tsx @@ -4,7 +4,7 @@ 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/enrollments/enrollmentsSlice' +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"; @@ -12,7 +12,7 @@ import { DataGrid, GridColDef, } from '@mui/x-data-grid'; -import {loadColumns} from "./configureEnrollmentsCols"; +import {loadColumns} from "./configureLeadsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; @@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles"; const perPage = 10 -const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleLeads = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -40,7 +40,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid }, ]); - const { enrollments, loading, count, notify: enrollmentsNotify, refetch } = useAppSelector((state) => state.enrollments) + 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); @@ -60,10 +60,10 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid }; useEffect(() => { - if (enrollmentsNotify.showNotification) { - notify(enrollmentsNotify.typeNotification, enrollmentsNotify.textNotification); + if (leadsNotify.showNotification) { + notify(leadsNotify.typeNotification, leadsNotify.textNotification); } - }, [enrollmentsNotify.showNotification]); + }, [leadsNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -177,7 +177,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid loadColumns( handleDeleteModalAction, - `enrollments`, + `leads`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -215,7 +215,7 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={enrollments ?? []} + rows={leads ?? []} columns={columns} initialState={{ pagination: { @@ -460,4 +460,4 @@ const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid ) } -export default TableSampleEnrollments +export default TableSampleLeads diff --git a/frontend/src/components/Enrollments/configureEnrollmentsCols.tsx b/frontend/src/components/Leads/configureLeadsCols.tsx similarity index 76% rename from frontend/src/components/Enrollments/configureEnrollmentsCols.tsx rename to frontend/src/components/Leads/configureLeadsCols.tsx index dfe9017..53cc701 100644 --- a/frontend/src/components/Enrollments/configureEnrollmentsCols.tsx +++ b/frontend/src/components/Leads/configureLeadsCols.tsx @@ -37,13 +37,13 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_ENROLLMENTS') + const hasUpdatePermission = hasPermission(user, 'UPDATE_LEADS') return [ { - field: 'enrollment_label', - headerName: 'EnrollmentLabel', + field: 'name', + headerName: 'Name', flex: 1, minWidth: 120, filterable: false, @@ -57,8 +57,83 @@ export const loadColumns = async ( }, { - field: 'student', - headerName: 'Student', + field: 'company', + headerName: 'Company', + 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: 'source', + headerName: 'Source', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'status', + headerName: 'Status', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'owner', + headerName: 'Owner', flex: 1, minWidth: 120, filterable: false, @@ -79,63 +154,8 @@ export const loadColumns = async ( }, { - field: 'course', - headerName: 'Course', - 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('courses'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - - { - field: 'enrolled_at', - headerName: 'EnrolledAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.enrolled_at), - - }, - - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'progress_percent', - headerName: 'ProgressPercent', + field: 'estimated_value', + headerName: 'EstimatedValue', flex: 1, minWidth: 120, filterable: false, @@ -150,8 +170,8 @@ export const loadColumns = async ( }, { - field: 'price_paid', - headerName: 'PricePaid', + field: 'notes', + headerName: 'Notes', flex: 1, minWidth: 120, filterable: false, @@ -161,8 +181,7 @@ export const loadColumns = async ( editable: hasUpdatePermission, - type: 'number', - + }, { @@ -178,8 +197,8 @@ export const loadColumns = async ( {NavBarItemComponentContents}
    -} +} \ No newline at end of file diff --git a/frontend/src/components/Pipeline_stages/CardPipeline_stages.tsx b/frontend/src/components/Pipeline_stages/CardPipeline_stages.tsx new file mode 100644 index 0000000..00cba31 --- /dev/null +++ b/frontend/src/components/Pipeline_stages/CardPipeline_stages.tsx @@ -0,0 +1,147 @@ +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.title} + + + +
      + +
      +
      +
      + + +
      +
      Title
      +
      +
      + { item.title } +
      +
      +
      + + + + +
      +
      Order
      +
      +
      + { item.order } +
      +
      +
      + + + + +
      +
      Probability
      +
      +
      + { item.probability } +
      +
      +
      + + + + +
      +
      IsDefault
      +
      +
      + { dataFormatter.booleanFormatter(item.is_default) } +
      +
      +
      + + + +
      +
    • + ))} + {!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 new file mode 100644 index 0000000..acb41b4 --- /dev/null +++ b/frontend/src/components/Pipeline_stages/ListPipeline_stages.tsx @@ -0,0 +1,112 @@ +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' + } + > + + +
    +

    Title

    +

    { item.title }

    +
    + + + + +
    +

    Order

    +

    { item.order }

    +
    + + + + +
    +

    Probability

    +

    { item.probability }

    +
    + + + + +
    +

    IsDefault

    +

    { dataFormatter.booleanFormatter(item.is_default) }

    +
    + + + + + +
    +
    +
    + ))} + {!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 new file mode 100644 index 0000000..f99faf7 --- /dev/null +++ b/frontend/src/components/Pipeline_stages/TablePipeline_stages.tsx @@ -0,0 +1,463 @@ +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"; + + + +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?

    +
    + + + {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 new file mode 100644 index 0000000..554989c --- /dev/null +++ b/frontend/src/components/Pipeline_stages/configurePipeline_stagesCols.tsx @@ -0,0 +1,131 @@ +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: 'title', + headerName: 'Title', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'order', + headerName: 'Order', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'number', + + }, + + { + field: 'probability', + headerName: 'Probability', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'number', + + }, + + { + field: 'is_default', + headerName: 'IsDefault', + 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 7158b26..f9d09eb 100644 --- a/frontend/src/helpers/dataFormatter.js +++ b/frontend/src/helpers/dataFormatter.js @@ -103,68 +103,89 @@ export default { - coursesManyListFormatter(val) { + pipeline_stagesManyListFormatter(val) { if (!val || !val.length) return [] return val.map((item) => item.title) }, - coursesOneListFormatter(val) { + pipeline_stagesOneListFormatter(val) { if (!val) return '' return val.title }, - coursesManyListFormatterEdit(val) { + pipeline_stagesManyListFormatterEdit(val) { if (!val || !val.length) return [] return val.map((item) => { return {id: item.id, label: item.title} }); }, - coursesOneListFormatterEdit(val) { + pipeline_stagesOneListFormatterEdit(val) { if (!val) return '' return {label: val.title, id: val.id} }, - lessonsManyListFormatter(val) { + leadsManyListFormatter(val) { + if (!val || !val.length) return [] + return val.map((item) => item.name) + }, + leadsOneListFormatter(val) { + if (!val) return '' + return val.name + }, + leadsManyListFormatterEdit(val) { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.name} + }); + }, + leadsOneListFormatterEdit(val) { + if (!val) return '' + return {label: val.name, id: val.id} + }, + + + + contactsManyListFormatter(val) { + if (!val || !val.length) return [] + return val.map((item) => item.name) + }, + contactsOneListFormatter(val) { + if (!val) return '' + return val.name + }, + contactsManyListFormatterEdit(val) { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.name} + }); + }, + contactsOneListFormatterEdit(val) { + if (!val) return '' + return {label: val.name, id: val.id} + }, + + + + dealsManyListFormatter(val) { if (!val || !val.length) return [] return val.map((item) => item.title) }, - lessonsOneListFormatter(val) { + dealsOneListFormatter(val) { if (!val) return '' return val.title }, - lessonsManyListFormatterEdit(val) { + dealsManyListFormatterEdit(val) { if (!val || !val.length) return [] return val.map((item) => { return {id: item.id, label: item.title} }); }, - lessonsOneListFormatterEdit(val) { + dealsOneListFormatterEdit(val) { if (!val) return '' return {label: val.title, id: val.id} }, - enrollmentsManyListFormatter(val) { - if (!val || !val.length) return [] - return val.map((item) => item.enrollment_label) - }, - enrollmentsOneListFormatter(val) { - if (!val) return '' - return val.enrollment_label - }, - enrollmentsManyListFormatterEdit(val) { - if (!val || !val.length) return [] - return val.map((item) => { - return {id: item.id, label: item.enrollment_label} - }); - }, - enrollmentsOneListFormatterEdit(val) { - if (!val) return '' - return {label: val.enrollment_label, id: val.id} - }, - - - } diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx index 1b9907d..26c3572 100644 --- a/frontend/src/layouts/Authenticated.tsx +++ b/frontend/src/layouts/Authenticated.tsx @@ -1,5 +1,4 @@ -import React, { ReactNode, useEffect } from 'react' -import { useState } from 'react' +import React, { ReactNode, useEffect, useState } from 'react' import jwt from 'jsonwebtoken'; import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js' import menuAside from '../menuAside' @@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
    ) -} +} \ No newline at end of file diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 9f26251..4b20dfb 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -33,36 +33,44 @@ const menuAside: MenuAsideItem[] = [ permissions: 'READ_PERMISSIONS' }, { - href: '/courses/courses-list', - label: 'Courses', + href: '/pipeline_stages/pipeline_stages-list', + label: 'Pipeline stages', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiSchool' in icon ? icon['mdiSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_COURSES' + icon: 'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_PIPELINE_STAGES' }, { - href: '/lessons/lessons-list', - label: 'Lessons', + href: '/leads/leads-list', + label: 'Leads', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiBookOpen' in icon ? icon['mdiBookOpen' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_LESSONS' + icon: 'mdiAccountPlus' in icon ? icon['mdiAccountPlus' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_LEADS' }, { - href: '/enrollments/enrollments-list', - label: 'Enrollments', + href: '/contacts/contacts-list', + label: 'Contacts', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiAccountMultiple' in icon ? icon['mdiAccountMultiple' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ENROLLMENTS' + icon: 'mdiAccount' in icon ? icon['mdiAccount' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_CONTACTS' }, { - href: '/progress/progress-list', - label: 'Progress', + href: '/deals/deals-list', + label: 'Deals', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiCheckCircle' in icon ? icon['mdiCheckCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_PROGRESS' + icon: 'mdiBriefcase' in icon ? icon['mdiBriefcase' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_DEALS' + }, + { + href: '/activities/activities-list', + label: 'Activities', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiCalendar' in icon ? icon['mdiCalendar' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_ACTIVITIES' }, { href: '/profile', diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index d9c1a6e..38fa31f 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -149,10 +149,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { setStepsEnabled(false); }; - const title = 'Instructor-Student LMS' - const description = "Instructor-student LMS for courses, lessons, enrollments, and progress tracking." + const title = 'Sales Pipeline CRM' + const description = "Sales Pipeline CRM for managing leads, deals, contacts, activities and automated follow-ups." const url = "https://flatlogic.com/" - const image = "https://project-screens.s3.amazonaws.com/screenshots/37612/app-hero-20260120-101908.png" + const image = "https://project-screens.s3.amazonaws.com/screenshots/37742/app-hero-20260123-114942.png" const imageWidth = '1920' const imageHeight = '960' diff --git a/frontend/src/pages/courses/[coursesId].tsx b/frontend/src/pages/activities/[activitiesId].tsx similarity index 60% rename from frontend/src/pages/courses/[coursesId].tsx rename to frontend/src/pages/activities/[activitiesId].tsx index 269e2a0..6eba2a3 100644 --- a/frontend/src/pages/courses/[coursesId].tsx +++ b/frontend/src/pages/activities/[activitiesId].tsx @@ -25,7 +25,7 @@ import { SelectFieldMany } from "../../components/SelectFieldMany"; import { SwitchField } from '../../components/SwitchField' import {RichTextField} from "../../components/RichTextField"; -import { update, fetch } from '../../stores/courses/coursesSlice' +import { update, fetch } from '../../stores/activities/activitiesSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import {saveFile} from "../../helpers/fileSaver"; @@ -34,13 +34,13 @@ import ImageField from "../../components/ImageField"; -const EditCourses = () => { +const EditActivities = () => { const router = useRouter() const dispatch = useAppDispatch() const initVals = { - 'title': '', + 'subject': '', @@ -72,8 +72,6 @@ const EditCourses = () => { - description: '', - @@ -86,7 +84,7 @@ const EditCourses = () => { - + activity_type: '', @@ -94,13 +92,9 @@ const EditCourses = () => { - - - - @@ -112,18 +106,14 @@ const EditCourses = () => { - + start: new Date(), - - instructor: null, - - @@ -132,27 +122,19 @@ const EditCourses = () => { - - - category: '', - - - - - - + end: new Date(), @@ -168,16 +150,6 @@ const EditCourses = () => { - level: '', - - - - - - - - - @@ -192,27 +164,21 @@ const EditCourses = () => { - + completed: false, - - thumbnail: [], - - - - @@ -220,8 +186,6 @@ const EditCourses = () => { - published: false, - @@ -234,19 +198,15 @@ const EditCourses = () => { - - - + owner: null, - - start_date: new Date(), @@ -262,8 +222,6 @@ const EditCourses = () => { - - @@ -272,15 +230,11 @@ const EditCourses = () => { - - - end_date: new Date(), + related_deal: null, - - @@ -290,10 +244,6 @@ const EditCourses = () => { - - - 'price': '', - @@ -308,23 +258,17 @@ const EditCourses = () => { - + related_lead: null, - - - - - 'language': '', - - + notes: '', @@ -351,44 +295,44 @@ const EditCourses = () => { } const [initialValues, setInitialValues] = useState(initVals) - const { courses } = useAppSelector((state) => state.courses) + const { activities } = useAppSelector((state) => state.activities) - const { coursesId } = router.query + const { activitiesId } = router.query useEffect(() => { - dispatch(fetch({ id: coursesId })) - }, [coursesId]) + dispatch(fetch({ id: activitiesId })) + }, [activitiesId]) useEffect(() => { - if (typeof courses === 'object') { - setInitialValues(courses) + if (typeof activities === 'object') { + setInitialValues(activities) } - }, [courses]) + }, [activities]) useEffect(() => { - if (typeof courses === 'object') { + if (typeof activities === 'object') { const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (courses)[el]) + Object.keys(initVals).forEach(el => newInitialVal[el] = (activities)[el]) setInitialValues(newInitialVal); } - }, [courses]) + }, [activities]) const handleSubmit = async (data) => { - await dispatch(update({ id: coursesId, data })) - await router.push('/courses/courses-list') + await dispatch(update({ id: activitiesId, data })) + await router.push('/activities/activities-list') } return ( <> - {getPageTitle('Edit courses')} + {getPageTitle('Edit activities')} - + {''} @@ -402,11 +346,11 @@ const EditCourses = () => { @@ -442,14 +386,6 @@ const EditCourses = () => { - - - - @@ -460,106 +396,18 @@ const EditCourses = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - + - + - - - + @@ -588,21 +436,67 @@ const EditCourses = () => { + + setInitialValues({...initialValues, 'start': date})} + /> + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + setInitialValues({...initialValues, 'end': date})} + /> + + + + + @@ -634,54 +528,10 @@ const EditCourses = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -708,21 +558,6 @@ const EditCourses = () => { - - setInitialValues({...initialValues, 'start_date': date})} - /> - - @@ -731,85 +566,36 @@ const EditCourses = () => { - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'end_date': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - + + name='owner' + id='owner' + component={SelectField} + options={initialValues.owner} + itemRef={'users'} + + + showField={'firstName'} + + + + + + + + + + + + + + + + + > - - - - - - - - - - - + @@ -822,18 +608,137 @@ const EditCourses = () => { - + + + + + + + + + + + + + + + + + + + + + name='related_deal' + id='related_deal' + component={SelectField} + options={initialValues.related_deal} + itemRef={'deals'} + + + + + + + + + + + + + + + showField={'title'} + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -863,7 +768,7 @@ const EditCourses = () => { - router.push('/courses/courses-list')}/> + router.push('/activities/activities-list')}/> @@ -873,11 +778,11 @@ const EditCourses = () => { ) } -EditCourses.getLayout = function getLayout(page: ReactElement) { +EditActivities.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -885,4 +790,4 @@ EditCourses.getLayout = function getLayout(page: ReactElement) { ) } -export default EditCourses +export default EditActivities diff --git a/frontend/src/pages/courses/courses-edit.tsx b/frontend/src/pages/activities/activities-edit.tsx similarity index 61% rename from frontend/src/pages/courses/courses-edit.tsx rename to frontend/src/pages/activities/activities-edit.tsx index a7e1d49..5e80a91 100644 --- a/frontend/src/pages/courses/courses-edit.tsx +++ b/frontend/src/pages/activities/activities-edit.tsx @@ -25,7 +25,7 @@ import { SelectFieldMany } from "../../components/SelectFieldMany"; import { SwitchField } from '../../components/SwitchField' import {RichTextField} from "../../components/RichTextField"; -import { update, fetch } from '../../stores/courses/coursesSlice' +import { update, fetch } from '../../stores/activities/activitiesSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import {saveFile} from "../../helpers/fileSaver"; @@ -34,13 +34,13 @@ import ImageField from "../../components/ImageField"; -const EditCoursesPage = () => { +const EditActivitiesPage = () => { const router = useRouter() const dispatch = useAppDispatch() const initVals = { - 'title': '', + 'subject': '', @@ -72,8 +72,6 @@ const EditCoursesPage = () => { - description: '', - @@ -86,7 +84,7 @@ const EditCoursesPage = () => { - + activity_type: '', @@ -94,13 +92,9 @@ const EditCoursesPage = () => { - - - - @@ -112,18 +106,14 @@ const EditCoursesPage = () => { - + start: new Date(), - - instructor: null, - - @@ -132,27 +122,19 @@ const EditCoursesPage = () => { - - - category: '', - - - - - - + end: new Date(), @@ -168,16 +150,6 @@ const EditCoursesPage = () => { - level: '', - - - - - - - - - @@ -192,27 +164,21 @@ const EditCoursesPage = () => { - + completed: false, - - thumbnail: [], - - - - @@ -220,8 +186,6 @@ const EditCoursesPage = () => { - published: false, - @@ -234,19 +198,15 @@ const EditCoursesPage = () => { - - - + owner: null, - - start_date: new Date(), @@ -262,8 +222,6 @@ const EditCoursesPage = () => { - - @@ -272,15 +230,11 @@ const EditCoursesPage = () => { - - - end_date: new Date(), + related_deal: null, - - @@ -290,10 +244,6 @@ const EditCoursesPage = () => { - - - 'price': '', - @@ -308,23 +258,17 @@ const EditCoursesPage = () => { - + related_lead: null, - - - - - 'language': '', - - + notes: '', @@ -351,7 +295,7 @@ const EditCoursesPage = () => { } const [initialValues, setInitialValues] = useState(initVals) - const { courses } = useAppSelector((state) => state.courses) + const { activities } = useAppSelector((state) => state.activities) const { id } = router.query @@ -361,31 +305,31 @@ const EditCoursesPage = () => { }, [id]) useEffect(() => { - if (typeof courses === 'object') { - setInitialValues(courses) + if (typeof activities === 'object') { + setInitialValues(activities) } - }, [courses]) + }, [activities]) useEffect(() => { - if (typeof courses === 'object') { + if (typeof activities === 'object') { const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (courses)[el]) + Object.keys(initVals).forEach(el => newInitialVal[el] = (activities)[el]) setInitialValues(newInitialVal); } - }, [courses]) + }, [activities]) const handleSubmit = async (data) => { await dispatch(update({ id: id, data })) - await router.push('/courses/courses-list') + await router.push('/activities/activities-list') } return ( <> - {getPageTitle('Edit courses')} + {getPageTitle('Edit activities')} - + {''} @@ -399,11 +343,11 @@ const EditCoursesPage = () => { @@ -439,14 +383,6 @@ const EditCoursesPage = () => { - - - - @@ -457,106 +393,18 @@ const EditCoursesPage = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - + - + - - - + @@ -585,21 +433,67 @@ const EditCoursesPage = () => { + + setInitialValues({...initialValues, 'start': date})} + /> + + + + - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + setInitialValues({...initialValues, 'end': date})} + /> + + + + + @@ -631,54 +525,10 @@ const EditCoursesPage = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -705,21 +555,6 @@ const EditCoursesPage = () => { - - setInitialValues({...initialValues, 'start_date': date})} - /> - - @@ -728,85 +563,36 @@ const EditCoursesPage = () => { - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'end_date': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - + + name='owner' + id='owner' + component={SelectField} + options={initialValues.owner} + itemRef={'users'} + + + showField={'firstName'} + + + + + + + + + + + + + + + + + > - - - - - - - - - - - + @@ -819,18 +605,137 @@ const EditCoursesPage = () => { - + + + + + + + + + + + + + + + + + + + + + name='related_deal' + id='related_deal' + component={SelectField} + options={initialValues.related_deal} + itemRef={'deals'} + + + + + + + + + + + + + + + showField={'title'} + + + + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -860,7 +765,7 @@ const EditCoursesPage = () => { - router.push('/courses/courses-list')}/> + router.push('/activities/activities-list')}/> @@ -870,11 +775,11 @@ const EditCoursesPage = () => { ) } -EditCoursesPage.getLayout = function getLayout(page: ReactElement) { +EditActivitiesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -882,4 +787,4 @@ EditCoursesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default EditCoursesPage +export default EditActivitiesPage diff --git a/frontend/src/pages/courses/courses-list.tsx b/frontend/src/pages/activities/activities-list.tsx similarity index 74% rename from frontend/src/pages/courses/courses-list.tsx rename to frontend/src/pages/activities/activities-list.tsx index 68d2b76..a0ecdaa 100644 --- a/frontend/src/pages/courses/courses-list.tsx +++ b/frontend/src/pages/activities/activities-list.tsx @@ -7,21 +7,21 @@ import LayoutAuthenticated from '../../layouts/Authenticated' import SectionMain from '../../components/SectionMain' import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' import { getPageTitle } from '../../config' -import TableCourses from '../../components/Courses/TableCourses' +import TableActivities from '../../components/Activities/TableActivities' 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/courses/coursesSlice'; +import {setRefetch, uploadCsv} from '../../stores/activities/activitiesSlice'; import {hasPermission} from "../../helpers/userPermissions"; -const CoursesTablesPage = () => { +const ActivitiesTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -34,20 +34,28 @@ const CoursesTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Description', title: 'description'},{label: 'Language', title: 'language'}, + const [filters] = useState([{label: 'Subject', title: 'subject'},{label: 'Notes', title: 'notes'}, - {label: 'Price', title: 'price', number: 'true'}, - {label: 'StartDate', title: 'start_date', date: 'true'},{label: 'EndDate', title: 'end_date', date: 'true'}, + + {label: 'Start', title: 'start', date: 'true'},{label: 'End', title: 'end', date: 'true'}, - {label: 'Instructor', title: 'instructor'}, + {label: 'Owner', title: 'owner'}, + + + + {label: 'RelatedDeal', title: 'related_deal'}, + + + + {label: 'RelatedLead', title: 'related_lead'}, - {label: 'Category', title: 'category', type: 'enum', options: ['Programming','Design','Math','Language','Business','Other']},{label: 'Level', title: 'level', type: 'enum', options: ['Beginner','Intermediate','Advanced']}, + {label: 'ActivityType', title: 'activity_type', type: 'enum', options: ['Call','Meeting','Email','Task','FollowUp']}, ]); - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_COURSES'); + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ACTIVITIES'); const addFilter = () => { @@ -64,13 +72,13 @@ const CoursesTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getCoursesCSV = async () => { - const response = await axios({url: '/courses?filetype=csv', method: 'GET',responseType: 'blob'}); + const getActivitiesCSV = async () => { + const response = await axios({url: '/activities?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 = 'coursesCSV.csv' + link.download = 'activitiesCSV.csv' link.click() }; @@ -90,15 +98,15 @@ const CoursesTablesPage = () => { return ( <> - {getPageTitle('Courses')} + {getPageTitle('Activities')} - + {''} - {hasCreatePermission && } + {hasCreatePermission && } { label='Filter' onClick={addFilter} /> - + {hasCreatePermission && ( {
    - Switch to Table + Switch to Table
    - { ) } -CoursesTablesPage.getLayout = function getLayout(page: ReactElement) { +ActivitiesTablesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -167,4 +175,4 @@ CoursesTablesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default CoursesTablesPage +export default ActivitiesTablesPage diff --git a/frontend/src/pages/courses/courses-new.tsx b/frontend/src/pages/activities/activities-new.tsx similarity index 58% rename from frontend/src/pages/courses/courses-new.tsx rename to frontend/src/pages/activities/activities-new.tsx index e9c0f25..aba59f7 100644 --- a/frontend/src/pages/courses/courses-new.tsx +++ b/frontend/src/pages/activities/activities-new.tsx @@ -22,7 +22,7 @@ import { SelectField } from '../../components/SelectField' import { SelectFieldMany } from "../../components/SelectFieldMany"; import {RichTextField} from "../../components/RichTextField"; -import { create } from '../../stores/courses/coursesSlice' +import { create } from '../../stores/activities/activitiesSlice' import { useAppDispatch } from '../../stores/hooks' import { useRouter } from 'next/router' import moment from 'moment'; @@ -30,7 +30,7 @@ import moment from 'moment'; const initialValues = { - title: '', + subject: '', @@ -48,7 +48,6 @@ const initialValues = { - description: '', @@ -56,6 +55,7 @@ const initialValues = { + activity_type: 'Call', @@ -68,12 +68,12 @@ const initialValues = { + start: '', - instructor: '', @@ -84,10 +84,10 @@ const initialValues = { + end: '', - category: 'Programming', @@ -101,10 +101,10 @@ const initialValues = { + completed: false, - level: 'Beginner', @@ -122,7 +122,7 @@ const initialValues = { - thumbnail: [], + owner: '', @@ -134,11 +134,11 @@ const initialValues = { - published: false, + related_deal: '', @@ -149,52 +149,19 @@ const initialValues = { - start_date: '', + related_lead: '', - - - - - end_date: '', - - - - - - - - - - - price: '', - - - - - - - - - - - - - - - - language: '', - - + notes: '', @@ -210,7 +177,7 @@ const initialValues = { } -const CoursesNew = () => { +const ActivitiesNew = () => { const router = useRouter() const dispatch = useAppDispatch() @@ -222,7 +189,7 @@ const CoursesNew = () => { const handleSubmit = async (data) => { await dispatch(create(data)) - await router.push('/courses/courses-list') + await router.push('/activities/activities-list') } return ( <> @@ -242,8 +209,8 @@ const CoursesNew = () => { dateRangeStart && dateRangeEnd ? { ...initialValues, - start_date: moment(dateRangeStart).format('YYYY-MM-DDTHH:mm'), - end_date: moment(dateRangeEnd).format('YYYY-MM-DDTHH:mm'), + start: moment(dateRangeStart).format('YYYY-MM-DDTHH:mm'), + end: moment(dateRangeEnd).format('YYYY-MM-DDTHH:mm'), } : initialValues } @@ -254,11 +221,11 @@ const CoursesNew = () => { @@ -292,13 +259,6 @@ const CoursesNew = () => { - - - @@ -309,77 +269,18 @@ const CoursesNew = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - + - + - + - + - - - + @@ -406,20 +307,50 @@ const CoursesNew = () => { + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -454,48 +385,10 @@ const CoursesNew = () => { - - - + - - - - - - - - - - - - - - - - - - - - - - - - @@ -520,14 +413,16 @@ const CoursesNew = () => { - - + + + + + + + + + + @@ -556,14 +451,8 @@ const CoursesNew = () => { - - + + @@ -588,43 +477,12 @@ const CoursesNew = () => { - - - - - - - - - - - - - - - - - - - - - - + + @@ -639,6 +497,22 @@ const CoursesNew = () => { + + + + + + + + + + + + @@ -655,7 +529,7 @@ const CoursesNew = () => { - router.push('/courses/courses-list')}/> + router.push('/activities/activities-list')}/> @@ -665,11 +539,11 @@ const CoursesNew = () => { ) } -CoursesNew.getLayout = function getLayout(page: ReactElement) { +ActivitiesNew.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -677,4 +551,4 @@ CoursesNew.getLayout = function getLayout(page: ReactElement) { ) } -export default CoursesNew +export default ActivitiesNew diff --git a/frontend/src/pages/courses/courses-table.tsx b/frontend/src/pages/activities/activities-table.tsx similarity index 75% rename from frontend/src/pages/courses/courses-table.tsx rename to frontend/src/pages/activities/activities-table.tsx index cf7250f..1d2b9b3 100644 --- a/frontend/src/pages/courses/courses-table.tsx +++ b/frontend/src/pages/activities/activities-table.tsx @@ -7,21 +7,21 @@ import LayoutAuthenticated from '../../layouts/Authenticated' import SectionMain from '../../components/SectionMain' import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' import { getPageTitle } from '../../config' -import TableCourses from '../../components/Courses/TableCourses' +import TableActivities from '../../components/Activities/TableActivities' 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/courses/coursesSlice'; +import {setRefetch, uploadCsv} from '../../stores/activities/activitiesSlice'; import {hasPermission} from "../../helpers/userPermissions"; -const CoursesTablesPage = () => { +const ActivitiesTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -34,20 +34,28 @@ const CoursesTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Description', title: 'description'},{label: 'Language', title: 'language'}, + const [filters] = useState([{label: 'Subject', title: 'subject'},{label: 'Notes', title: 'notes'}, - {label: 'Price', title: 'price', number: 'true'}, - {label: 'StartDate', title: 'start_date', date: 'true'},{label: 'EndDate', title: 'end_date', date: 'true'}, + + {label: 'Start', title: 'start', date: 'true'},{label: 'End', title: 'end', date: 'true'}, - {label: 'Instructor', title: 'instructor'}, + {label: 'Owner', title: 'owner'}, + + + + {label: 'RelatedDeal', title: 'related_deal'}, + + + + {label: 'RelatedLead', title: 'related_lead'}, - {label: 'Category', title: 'category', type: 'enum', options: ['Programming','Design','Math','Language','Business','Other']},{label: 'Level', title: 'level', type: 'enum', options: ['Beginner','Intermediate','Advanced']}, + {label: 'ActivityType', title: 'activity_type', type: 'enum', options: ['Call','Meeting','Email','Task','FollowUp']}, ]); - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_COURSES'); + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ACTIVITIES'); const addFilter = () => { @@ -64,13 +72,13 @@ const CoursesTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getCoursesCSV = async () => { - const response = await axios({url: '/courses?filetype=csv', method: 'GET',responseType: 'blob'}); + const getActivitiesCSV = async () => { + const response = await axios({url: '/activities?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 = 'coursesCSV.csv' + link.download = 'activitiesCSV.csv' link.click() }; @@ -90,15 +98,15 @@ const CoursesTablesPage = () => { return ( <> - {getPageTitle('Courses')} + {getPageTitle('Activities')} - + {''} - {hasCreatePermission && } + {hasCreatePermission && } { label='Filter' onClick={addFilter} /> - + {hasCreatePermission && ( {
    - + Back to calendar
    - { ) } -CoursesTablesPage.getLayout = function getLayout(page: ReactElement) { +ActivitiesTablesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -165,4 +173,4 @@ CoursesTablesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default CoursesTablesPage +export default ActivitiesTablesPage diff --git a/frontend/src/pages/progress/progress-view.tsx b/frontend/src/pages/activities/activities-view.tsx similarity index 60% rename from frontend/src/pages/progress/progress-view.tsx rename to frontend/src/pages/activities/activities-view.tsx index 9f4a6e7..5d53fcf 100644 --- a/frontend/src/pages/progress/progress-view.tsx +++ b/frontend/src/pages/activities/activities-view.tsx @@ -5,7 +5,7 @@ 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/progress/progressSlice' +import { fetch } from '../../stores/activities/activitiesSlice' import {saveFile} from "../../helpers/fileSaver"; import dataFormatter from '../../helpers/dataFormatter'; import ImageField from "../../components/ImageField"; @@ -21,10 +21,10 @@ import {SwitchField} from "../../components/SwitchField"; import FormField from "../../components/FormField"; -const ProgressView = () => { +const ActivitiesView = () => { const router = useRouter() const dispatch = useAppDispatch() - const { progress } = useAppSelector((state) => state.progress) + const { activities } = useAppSelector((state) => state.activities) const { id } = router.query; @@ -42,24 +42,105 @@ const ProgressView = () => { return ( <> - {getPageTitle('View progress')} + {getPageTitle('View activities')} - + +
    +

    Subject

    +

    {activities?.subject}

    +
    + - -