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 09b66a6..f7da3b1 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.

-

Store Operations Manager

-

Manage products, customers, orders, payments, and fulfillment workflows for retail operations.

+

CourseFlow LMS

+

CourseFlow LMS: instructor and student workflows for courses, lessons, enrollments, and progress tracking.

App Logo CREATE DATABASE db_store_operations_manager;` + - `postgres=> CREATE DATABASE db_courseflow_lms;` - Then give that new user privileges to the new database then quit the `psql`. - - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_store_operations_manager TO admin;` + - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_courseflow_lms TO admin;` - `postgres=> \q` ------------ diff --git a/backend/package.json b/backend/package.json index ffbfb37..539bf4f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { - "name": "storeoperationsmanager", - "description": "Store Operations Manager - template backend", + "name": "courseflowlms", + "description": "CourseFlow LMS - template backend", "scripts": { "start": "npm run db:migrate && npm run db:seed && npm run watch", "db:migrate": "sequelize-cli db:migrate", diff --git a/backend/src/config.js b/backend/src/config.js index cbeace4..a0cfedb 100644 --- a/backend/src/config.js +++ b/backend/src/config.js @@ -11,15 +11,15 @@ const config = { bcrypt: { saltRounds: 12 }, - admin_pass: "b5fc3cee", - user_pass: "51bd3a434c2c", + admin_pass: "578d9f61", + user_pass: "ac39c47254f1", admin_email: "admin@flatlogic.com", providers: { LOCAL: 'local', GOOGLE: 'google', MICROSOFT: 'microsoft' }, - secret_key: process.env.SECRET_KEY || 'b5fc3cee-cc82-4cf6-bbcd-51bd3a434c2c', + secret_key: process.env.SECRET_KEY || '578d9f61-c443-4a62-87c0-ac39c47254f1', 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: 'Store Operations Manager ', + from: 'CourseFlow LMS ', host: 'email-smtp.us-east-1.amazonaws.com', port: 587, auth: { @@ -56,11 +56,11 @@ const config = { - user: 'Support Agent', + user: 'Student', }, - project_uuid: 'b5fc3cee-cc82-4cf6-bbcd-51bd3a434c2c', + project_uuid: '578d9f61-c443-4a62-87c0-ac39c47254f1', 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 = 'city skyline at golden hour'; +config.pexelsQuery = 'open book with flowing pages'; 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/shipments.js b/backend/src/db/api/announcements.js similarity index 62% rename from backend/src/db/api/shipments.js rename to backend/src/db/api/announcements.js index 6d4c0f1..2976291 100644 --- a/backend/src/db/api/shipments.js +++ b/backend/src/db/api/announcements.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class ShipmentsDBApi { +module.exports = class AnnouncementsDBApi { @@ -17,33 +17,29 @@ module.exports = class ShipmentsDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const shipments = await db.shipments.create( + const announcements = await db.announcements.create( { id: data.id || undefined, - carrier: data.carrier + title: data.title || null , - tracking_number: data.tracking_number + content: data.content || null , - shipped_at: data.shipped_at + published_at: data.published_at || null , - delivered_at: data.delivered_at + is_active: data.is_active || - null - , - - status: data.status - || - null + false + , importHash: data.importHash || null, @@ -54,7 +50,7 @@ module.exports = class ShipmentsDBApi { ); - await shipments.setOrder( data.order || null, { + await announcements.setCourse( data.course || null, { transaction, }); @@ -63,7 +59,7 @@ module.exports = class ShipmentsDBApi { - return shipments; + return announcements; } @@ -72,32 +68,28 @@ module.exports = class ShipmentsDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const shipmentsData = data.map((item, index) => ({ + const announcementsData = data.map((item, index) => ({ id: item.id || undefined, - carrier: item.carrier + title: item.title || null , - tracking_number: item.tracking_number + content: item.content || null , - shipped_at: item.shipped_at + published_at: item.published_at || null , - delivered_at: item.delivered_at + is_active: item.is_active || - null - , - - status: item.status - || - null + false + , importHash: item.importHash || null, @@ -107,12 +99,12 @@ module.exports = class ShipmentsDBApi { })); // Bulk create items - const shipments = await db.shipments.bulkCreate(shipmentsData, { transaction }); + const announcements = await db.announcements.bulkCreate(announcementsData, { transaction }); // For each item created, replace relation files - return shipments; + return announcements; } static async update(id, data, options) { @@ -120,38 +112,35 @@ module.exports = class ShipmentsDBApi { const transaction = (options && options.transaction) || undefined; - const shipments = await db.shipments.findByPk(id, {}, {transaction}); + const announcements = await db.announcements.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.carrier !== undefined) updatePayload.carrier = data.carrier; + if (data.title !== undefined) updatePayload.title = data.title; - if (data.tracking_number !== undefined) updatePayload.tracking_number = data.tracking_number; + if (data.content !== undefined) updatePayload.content = data.content; - if (data.shipped_at !== undefined) updatePayload.shipped_at = data.shipped_at; + if (data.published_at !== undefined) updatePayload.published_at = data.published_at; - if (data.delivered_at !== undefined) updatePayload.delivered_at = data.delivered_at; - - - if (data.status !== undefined) updatePayload.status = data.status; + if (data.is_active !== undefined) updatePayload.is_active = data.is_active; updatePayload.updatedById = currentUser.id; - await shipments.update(updatePayload, {transaction}); + await announcements.update(updatePayload, {transaction}); - if (data.order !== undefined) { - await shipments.setOrder( + if (data.course !== undefined) { + await announcements.setCourse( - data.order, + data.course, { transaction } ); @@ -163,14 +152,14 @@ module.exports = class ShipmentsDBApi { - return shipments; + return announcements; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const shipments = await db.shipments.findAll({ + const announcements = await db.announcements.findAll({ where: { id: { [Op.in]: ids, @@ -180,53 +169,53 @@ module.exports = class ShipmentsDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of shipments) { + for (const record of announcements) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of shipments) { + for (const record of announcements) { await record.destroy({transaction}); } }); - return shipments; + return announcements; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const shipments = await db.shipments.findByPk(id, options); + const announcements = await db.announcements.findByPk(id, options); - await shipments.update({ + await announcements.update({ deletedBy: currentUser.id }, { transaction, }); - await shipments.destroy({ + await announcements.destroy({ transaction }); - return shipments; + return announcements; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const shipments = await db.shipments.findOne( + const announcements = await db.announcements.findOne( { where }, { transaction }, ); - if (!shipments) { - return shipments; + if (!announcements) { + return announcements; } - const output = shipments.get({plain: true}); + const output = announcements.get({plain: true}); @@ -240,7 +229,9 @@ module.exports = class ShipmentsDBApi { - output.order = await shipments.getOrder({ + + + output.course = await announcements.getCourse({ transaction }); @@ -271,15 +262,15 @@ module.exports = class ShipmentsDBApi { let include = [ { - model: db.orders, - as: 'order', + model: db.courses, + as: 'course', - where: filter.order ? { + where: filter.course ? { [Op.or]: [ - { id: { [Op.in]: filter.order.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.course.split('|').map(term => Utils.uuid(term)) } }, { - order_number: { - [Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + title: { + [Op.or]: filter.course.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -300,24 +291,24 @@ module.exports = class ShipmentsDBApi { } - if (filter.carrier) { + if (filter.title) { where = { ...where, [Op.and]: Utils.ilike( - 'shipments', - 'carrier', - filter.carrier, + 'announcements', + 'title', + filter.title, ), }; } - if (filter.tracking_number) { + if (filter.content) { where = { ...where, [Op.and]: Utils.ilike( - 'shipments', - 'tracking_number', - filter.tracking_number, + 'announcements', + 'content', + filter.content, ), }; } @@ -325,34 +316,16 @@ module.exports = class ShipmentsDBApi { - if (filter.calendarStart && filter.calendarEnd) { - where = { - ...where, - [Op.or]: [ - { - shipped_at: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - { - delivered_at: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - ], - }; - } - - if (filter.shipped_atRange) { - const [start, end] = filter.shipped_atRange; + if (filter.published_atRange) { + const [start, end] = filter.published_atRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - shipped_at: { - ...where.shipped_at, + published_at: { + ...where.published_at, [Op.gte]: start, }, }; @@ -361,32 +334,8 @@ module.exports = class ShipmentsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - shipped_at: { - ...where.shipped_at, - [Op.lte]: end, - }, - }; - } - } - - if (filter.delivered_atRange) { - const [start, end] = filter.delivered_atRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - delivered_at: { - ...where.delivered_at, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - delivered_at: { - ...where.delivered_at, + published_at: { + ...where.published_at, [Op.lte]: end, }, }; @@ -402,10 +351,10 @@ module.exports = class ShipmentsDBApi { } - if (filter.status) { + if (filter.is_active) { where = { ...where, - status: filter.status, + is_active: filter.is_active, }; } @@ -460,7 +409,7 @@ module.exports = class ShipmentsDBApi { } try { - const { rows, count } = await db.shipments.findAndCountAll(queryOptions); + const { rows, count } = await db.announcements.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -482,25 +431,25 @@ module.exports = class ShipmentsDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'shipments', - 'tracking_number', + 'announcements', + 'title', query, ), ], }; } - const records = await db.shipments.findAll({ - attributes: [ 'id', 'tracking_number' ], + const records = await db.announcements.findAll({ + attributes: [ 'id', 'title' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['tracking_number', 'ASC']], + orderBy: [['title', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.tracking_number, + label: record.title, })); } diff --git a/backend/src/db/api/certificates.js b/backend/src/db/api/certificates.js new file mode 100644 index 0000000..7695ca0 --- /dev/null +++ b/backend/src/db/api/certificates.js @@ -0,0 +1,491 @@ + +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 CertificatesDBApi { + + + + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const certificates = await db.certificates.create( + { + id: data.id || undefined, + + serial: data.serial + || + null + , + + issued_at: data.issued_at + || + null + , + + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + + await certificates.setStudent( data.student || null, { + transaction, + }); + + await certificates.setCourse( data.course || null, { + transaction, + }); + + + + + + await FileDBApi.replaceRelationFiles( + { + belongsTo: db.certificates.getTableName(), + belongsToColumn: 'file', + belongsToId: certificates.id, + }, + data.file, + options, + ); + + + return certificates; + } + + + 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 certificatesData = data.map((item, index) => ({ + id: item.id || undefined, + + serial: item.serial + || + null + , + + issued_at: item.issued_at + || + null + , + + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const certificates = await db.certificates.bulkCreate(certificatesData, { transaction }); + + // For each item created, replace relation files + + for (let i = 0; i < certificates.length; i++) { + await FileDBApi.replaceRelationFiles( + { + belongsTo: db.certificates.getTableName(), + belongsToColumn: 'file', + belongsToId: certificates[i].id, + }, + data[i].file, + options, + ); + } + + + return certificates; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || {id: null}; + const transaction = (options && options.transaction) || undefined; + + + const certificates = await db.certificates.findByPk(id, {}, {transaction}); + + + + + const updatePayload = {}; + + if (data.serial !== undefined) updatePayload.serial = data.serial; + + + if (data.issued_at !== undefined) updatePayload.issued_at = data.issued_at; + + + updatePayload.updatedById = currentUser.id; + + await certificates.update(updatePayload, {transaction}); + + + + if (data.student !== undefined) { + await certificates.setStudent( + + data.student, + + { transaction } + ); + } + + if (data.course !== undefined) { + await certificates.setCourse( + + data.course, + + { transaction } + ); + } + + + + + + + await FileDBApi.replaceRelationFiles( + { + belongsTo: db.certificates.getTableName(), + belongsToColumn: 'file', + belongsToId: certificates.id, + }, + data.file, + options, + ); + + + return certificates; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const certificates = await db.certificates.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of certificates) { + await record.update( + {deletedBy: currentUser.id}, + {transaction} + ); + } + for (const record of certificates) { + await record.destroy({transaction}); + } + }); + + + return certificates; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || {id: null}; + const transaction = (options && options.transaction) || undefined; + + const certificates = await db.certificates.findByPk(id, options); + + await certificates.update({ + deletedBy: currentUser.id + }, { + transaction, + }); + + await certificates.destroy({ + transaction + }); + + return certificates; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const certificates = await db.certificates.findOne( + { where }, + { transaction }, + ); + + if (!certificates) { + return certificates; + } + + const output = certificates.get({plain: true}); + + + + + + + + + + + + + + + + output.student = await certificates.getStudent({ + transaction + }); + + + output.course = await certificates.getCourse({ + transaction + }); + + + output.file = await certificates.getFile({ + 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: 'student', + + where: filter.student ? { + [Op.or]: [ + { id: { [Op.in]: filter.student.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}%` })) + } + }, + ] + } : {}, + + }, + + + + { + model: db.file, + as: 'file', + }, + + ]; + + if (filter) { + if (filter.id) { + where = { + ...where, + ['id']: Utils.uuid(filter.id), + }; + } + + + if (filter.serial) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'certificates', + 'serial', + filter.serial, + ), + }; + } + + + + + + + if (filter.issued_atRange) { + const [start, end] = filter.issued_atRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + issued_at: { + ...where.issued_at, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + issued_at: { + ...where.issued_at, + [Op.lte]: end, + }, + }; + } + } + + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true' + }; + } + + + + + + + + + + + if (filter.createdAtRange) { + const [start, end] = filter.createdAtRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + ['createdAt']: { + ...where.createdAt, + [Op.lte]: end, + }, + }; + } + } + } + + + + + const queryOptions = { + where, + include, + distinct: true, + order: filter.field && filter.sort + ? [[filter.field, filter.sort]] + : [['createdAt', 'desc']], + transaction: options?.transaction, + logging: console.log + }; + + if (!options?.countOnly) { + queryOptions.limit = limit ? Number(limit) : undefined; + queryOptions.offset = offset ? Number(offset) : undefined; + } + + try { + const { rows, count } = await db.certificates.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( + 'certificates', + 'serial', + query, + ), + ], + }; + } + + const records = await db.certificates.findAll({ + attributes: [ 'id', 'serial' ], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['serial', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.serial, + })); + } + + +}; + diff --git a/backend/src/db/api/categories.js b/backend/src/db/api/course_categories.js similarity index 77% rename from backend/src/db/api/categories.js rename to backend/src/db/api/course_categories.js index f4f42fc..20926b9 100644 --- a/backend/src/db/api/categories.js +++ b/backend/src/db/api/course_categories.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class CategoriesDBApi { +module.exports = class Course_categoriesDBApi { @@ -17,7 +17,7 @@ module.exports = class CategoriesDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const categories = await db.categories.create( + const course_categories = await db.course_categories.create( { id: data.id || undefined, @@ -26,6 +26,11 @@ module.exports = class CategoriesDBApi { null , + slug: data.slug + || + null + , + description: data.description || null @@ -39,16 +44,12 @@ module.exports = class CategoriesDBApi { ); - await categories.setParent( data.parent || null, { - transaction, - }); - - return categories; + return course_categories; } @@ -57,12 +58,17 @@ module.exports = class CategoriesDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const categoriesData = data.map((item, index) => ({ + const course_categoriesData = data.map((item, index) => ({ id: item.id || undefined, name: item.name || null + , + + slug: item.slug + || + null , description: item.description @@ -77,12 +83,12 @@ module.exports = class CategoriesDBApi { })); // Bulk create items - const categories = await db.categories.bulkCreate(categoriesData, { transaction }); + const course_categories = await db.course_categories.bulkCreate(course_categoriesData, { transaction }); // For each item created, replace relation files - return categories; + return course_categories; } static async update(id, data, options) { @@ -90,7 +96,7 @@ module.exports = class CategoriesDBApi { const transaction = (options && options.transaction) || undefined; - const categories = await db.categories.findByPk(id, {}, {transaction}); + const course_categories = await db.course_categories.findByPk(id, {}, {transaction}); @@ -100,38 +106,32 @@ module.exports = class CategoriesDBApi { if (data.name !== undefined) updatePayload.name = data.name; + if (data.slug !== undefined) updatePayload.slug = data.slug; + + if (data.description !== undefined) updatePayload.description = data.description; updatePayload.updatedById = currentUser.id; - await categories.update(updatePayload, {transaction}); + await course_categories.update(updatePayload, {transaction}); - if (data.parent !== undefined) { - await categories.setParent( - - data.parent, - - { transaction } - ); - } - - return categories; + return course_categories; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const categories = await db.categories.findAll({ + const course_categories = await db.course_categories.findAll({ where: { id: { [Op.in]: ids, @@ -141,59 +141,60 @@ module.exports = class CategoriesDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of categories) { + for (const record of course_categories) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of categories) { + for (const record of course_categories) { await record.destroy({transaction}); } }); - return categories; + return course_categories; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const categories = await db.categories.findByPk(id, options); + const course_categories = await db.course_categories.findByPk(id, options); - await categories.update({ + await course_categories.update({ deletedBy: currentUser.id }, { transaction, }); - await categories.destroy({ + await course_categories.destroy({ transaction }); - return categories; + return course_categories; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const categories = await db.categories.findOne( + const course_categories = await db.course_categories.findOne( { where }, { transaction }, ); - if (!categories) { - return categories; + if (!course_categories) { + return course_categories; } - const output = categories.get({plain: true}); + const output = course_categories.get({plain: true}); - output.products_category = await categories.getProducts_category({ + + output.courses_category = await course_categories.getCourses_category({ transaction }); @@ -205,10 +206,6 @@ module.exports = class CategoriesDBApi { - output.parent = await categories.getParent({ - transaction - }); - return output; @@ -235,23 +232,6 @@ module.exports = class CategoriesDBApi { let include = [ - { - model: db.categories, - as: 'parent', - - where: filter.parent ? { - [Op.or]: [ - { id: { [Op.in]: filter.parent.split('|').map(term => Utils.uuid(term)) } }, - { - name: { - [Op.or]: filter.parent.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) - } - }, - ] - } : {}, - - }, - ]; @@ -269,18 +249,29 @@ module.exports = class CategoriesDBApi { where = { ...where, [Op.and]: Utils.ilike( - 'categories', + 'course_categories', 'name', filter.name, ), }; } + if (filter.slug) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'course_categories', + 'slug', + filter.slug, + ), + }; + } + if (filter.description) { where = { ...where, [Op.and]: Utils.ilike( - 'categories', + 'course_categories', 'description', filter.description, ), @@ -303,8 +294,6 @@ module.exports = class CategoriesDBApi { - - if (filter.createdAtRange) { @@ -352,7 +341,7 @@ module.exports = class CategoriesDBApi { } try { - const { rows, count } = await db.categories.findAndCountAll(queryOptions); + const { rows, count } = await db.course_categories.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -374,7 +363,7 @@ module.exports = class CategoriesDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'categories', + 'course_categories', 'name', query, ), @@ -382,7 +371,7 @@ module.exports = class CategoriesDBApi { }; } - const records = await db.categories.findAll({ + const records = await db.course_categories.findAll({ attributes: [ 'id', 'name' ], where, limit: limit ? Number(limit) : undefined, diff --git a/backend/src/db/api/courses.js b/backend/src/db/api/courses.js new file mode 100644 index 0000000..44083f9 --- /dev/null +++ b/backend/src/db/api/courses.js @@ -0,0 +1,695 @@ + +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 CoursesDBApi { + + + + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const courses = await db.courses.create( + { + id: data.id || undefined, + + title: data.title + || + null + , + + short_description: data.short_description + || + null + , + + description: data.description + || + null + , + + level: data.level + || + null + , + + language: data.language + || + null + , + + price: data.price + || + null + , + + published: data.published + || + false + + , + + published_at: data.published_at + || + null + , + + duration: data.duration + || + null + , + + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + + await courses.setInstructor( data.instructor || null, { + transaction, + }); + + await courses.setCategory( data.category || null, { + transaction, + }); + + + + + + await FileDBApi.replaceRelationFiles( + { + belongsTo: db.courses.getTableName(), + belongsToColumn: 'thumbnail', + belongsToId: courses.id, + }, + data.thumbnail, + options, + ); + + + return courses; + } + + + 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 coursesData = data.map((item, index) => ({ + id: item.id || undefined, + + title: item.title + || + null + , + + short_description: item.short_description + || + null + , + + description: item.description + || + null + , + + level: item.level + || + null + , + + language: item.language + || + null + , + + price: item.price + || + null + , + + published: item.published + || + false + + , + + published_at: item.published_at + || + null + , + + duration: item.duration + || + null + , + + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const courses = await db.courses.bulkCreate(coursesData, { 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; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || {id: null}; + const transaction = (options && options.transaction) || undefined; + + + const courses = await db.courses.findByPk(id, {}, {transaction}); + + + + + const updatePayload = {}; + + if (data.title !== undefined) updatePayload.title = data.title; + + + if (data.short_description !== undefined) updatePayload.short_description = data.short_description; + + + if (data.description !== undefined) updatePayload.description = data.description; + + + if (data.level !== undefined) updatePayload.level = data.level; + + + if (data.language !== undefined) updatePayload.language = data.language; + + + if (data.price !== undefined) updatePayload.price = data.price; + + + if (data.published !== undefined) updatePayload.published = data.published; + + + if (data.published_at !== undefined) updatePayload.published_at = data.published_at; + + + if (data.duration !== undefined) updatePayload.duration = data.duration; + + + updatePayload.updatedById = currentUser.id; + + await courses.update(updatePayload, {transaction}); + + + + if (data.instructor !== undefined) { + await courses.setInstructor( + + data.instructor, + + { transaction } + ); + } + + if (data.category !== undefined) { + await courses.setCategory( + + data.category, + + { transaction } + ); + } + + + + + + + await FileDBApi.replaceRelationFiles( + { + belongsTo: db.courses.getTableName(), + belongsToColumn: 'thumbnail', + belongsToId: courses.id, + }, + data.thumbnail, + options, + ); + + + return courses; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const courses = await db.courses.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of courses) { + await record.update( + {deletedBy: currentUser.id}, + {transaction} + ); + } + for (const record of courses) { + await record.destroy({transaction}); + } + }); + + + return courses; + } + + 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); + + await courses.update({ + deletedBy: currentUser.id + }, { + transaction, + }); + + await courses.destroy({ + transaction + }); + + return courses; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const courses = await db.courses.findOne( + { where }, + { transaction }, + ); + + if (!courses) { + return courses; + } + + const output = courses.get({plain: true}); + + + + + + + + output.lessons_course = await courses.getLessons_course({ + transaction + }); + + + output.enrollments_course = await courses.getEnrollments_course({ + transaction + }); + + + + + + output.announcements_course = await courses.getAnnouncements_course({ + transaction + }); + + + output.certificates_course = await courses.getCertificates_course({ + transaction + }); + + + + output.instructor = await courses.getInstructor({ + transaction + }); + + + output.category = await courses.getCategory({ + transaction + }); + + + output.thumbnail = await courses.getThumbnail({ + 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: '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.course_categories, + as: 'category', + + where: filter.category ? { + [Op.or]: [ + { id: { [Op.in]: filter.category.split('|').map(term => Utils.uuid(term)) } }, + { + name: { + [Op.or]: filter.category.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + } + }, + ] + } : {}, + + }, + + + + { + model: db.file, + as: 'thumbnail', + }, + + ]; + + if (filter) { + if (filter.id) { + where = { + ...where, + ['id']: Utils.uuid(filter.id), + }; + } + + + if (filter.title) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'courses', + 'title', + filter.title, + ), + }; + } + + if (filter.short_description) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'courses', + 'short_description', + filter.short_description, + ), + }; + } + + 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.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, + [Op.lte]: end, + }, + }; + } + } + + if (filter.published_atRange) { + const [start, end] = filter.published_atRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + published_at: { + ...where.published_at, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + published_at: { + ...where.published_at, + [Op.lte]: end, + }, + }; + } + } + + if (filter.durationRange) { + const [start, end] = filter.durationRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + duration: { + ...where.duration, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + duration: { + ...where.duration, + [Op.lte]: end, + }, + }; + } + } + + + if (filter.active !== undefined) { + where = { + ...where, + active: filter.active === true || filter.active === 'true' + }; + } + + + if (filter.level) { + where = { + ...where, + level: filter.level, + }; + } + + if (filter.published) { + where = { + ...where, + published: filter.published, + }; + } + + + + + + + + + + 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.courses.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( + 'courses', + 'title', + query, + ), + ], + }; + } + + const records = await db.courses.findAll({ + attributes: [ 'id', 'title' ], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['title', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.title, + })); + } + + +}; + diff --git a/backend/src/db/api/products.js b/backend/src/db/api/enrollments.js similarity index 66% rename from backend/src/db/api/products.js rename to backend/src/db/api/enrollments.js index af4acac..9d255a9 100644 --- a/backend/src/db/api/products.js +++ b/backend/src/db/api/enrollments.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class ProductsDBApi { +module.exports = class EnrollmentsDBApi { @@ -17,37 +17,27 @@ module.exports = class ProductsDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const products = await db.products.create( + const enrollments = await db.enrollments.create( { id: data.id || undefined, - sku: data.sku + enrollment_code: data.enrollment_code || null , - name: data.name - || - null - , - - description: data.description - || - null - , - - price: data.price - || - null - , - - stock: data.stock + enrolled_at: data.enrolled_at || null , status: data.status || + null + , + + progress_percent: data.progress_percent + || null , @@ -59,7 +49,11 @@ module.exports = class ProductsDBApi { ); - await products.setCategory( data.category || null, { + await enrollments.setStudent( data.student || null, { + transaction, + }); + + await enrollments.setCourse( data.course || null, { transaction, }); @@ -69,16 +63,16 @@ module.exports = class ProductsDBApi { await FileDBApi.replaceRelationFiles( { - belongsTo: db.products.getTableName(), - belongsToColumn: 'images', - belongsToId: products.id, + belongsTo: db.enrollments.getTableName(), + belongsToColumn: 'certificate', + belongsToId: enrollments.id, }, - data.images, + data.certificate, options, ); - return products; + return enrollments; } @@ -87,30 +81,15 @@ module.exports = class ProductsDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const productsData = data.map((item, index) => ({ + const enrollmentsData = data.map((item, index) => ({ id: item.id || undefined, - sku: item.sku + enrollment_code: item.enrollment_code || null , - name: item.name - || - null - , - - description: item.description - || - null - , - - price: item.price - || - null - , - - stock: item.stock + enrolled_at: item.enrolled_at || null , @@ -118,6 +97,11 @@ module.exports = class ProductsDBApi { status: item.status || null + , + + progress_percent: item.progress_percent + || + null , importHash: item.importHash || null, @@ -127,24 +111,24 @@ module.exports = class ProductsDBApi { })); // Bulk create items - const products = await db.products.bulkCreate(productsData, { transaction }); + const enrollments = await db.enrollments.bulkCreate(enrollmentsData, { transaction }); // For each item created, replace relation files - for (let i = 0; i < products.length; i++) { + for (let i = 0; i < enrollments.length; i++) { await FileDBApi.replaceRelationFiles( { - belongsTo: db.products.getTableName(), - belongsToColumn: 'images', - belongsToId: products[i].id, + belongsTo: db.enrollments.getTableName(), + belongsToColumn: 'certificate', + belongsToId: enrollments[i].id, }, - data[i].images, + data[i].certificate, options, ); } - return products; + return enrollments; } static async update(id, data, options) { @@ -152,41 +136,44 @@ module.exports = class ProductsDBApi { const transaction = (options && options.transaction) || undefined; - const products = await db.products.findByPk(id, {}, {transaction}); + const enrollments = await db.enrollments.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.sku !== undefined) updatePayload.sku = data.sku; + if (data.enrollment_code !== undefined) updatePayload.enrollment_code = data.enrollment_code; - if (data.name !== undefined) updatePayload.name = data.name; - - - if (data.description !== undefined) updatePayload.description = data.description; - - - if (data.price !== undefined) updatePayload.price = data.price; - - - if (data.stock !== undefined) updatePayload.stock = data.stock; + if (data.enrolled_at !== undefined) updatePayload.enrolled_at = data.enrolled_at; if (data.status !== undefined) updatePayload.status = data.status; + if (data.progress_percent !== undefined) updatePayload.progress_percent = data.progress_percent; + + updatePayload.updatedById = currentUser.id; - await products.update(updatePayload, {transaction}); + await enrollments.update(updatePayload, {transaction}); - if (data.category !== undefined) { - await products.setCategory( + if (data.student !== undefined) { + await enrollments.setStudent( - data.category, + data.student, + + { transaction } + ); + } + + if (data.course !== undefined) { + await enrollments.setCourse( + + data.course, { transaction } ); @@ -199,23 +186,23 @@ module.exports = class ProductsDBApi { await FileDBApi.replaceRelationFiles( { - belongsTo: db.products.getTableName(), - belongsToColumn: 'images', - belongsToId: products.id, + belongsTo: db.enrollments.getTableName(), + belongsToColumn: 'certificate', + belongsToId: enrollments.id, }, - data.images, + data.certificate, options, ); - return products; + return enrollments; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const products = await db.products.findAll({ + const enrollments = await db.enrollments.findAll({ where: { id: { [Op.in]: ids, @@ -225,53 +212,53 @@ module.exports = class ProductsDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of products) { + for (const record of enrollments) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of products) { + for (const record of enrollments) { await record.destroy({transaction}); } }); - return products; + return enrollments; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const products = await db.products.findByPk(id, options); + const enrollments = await db.enrollments.findByPk(id, options); - await products.update({ + await enrollments.update({ deletedBy: currentUser.id }, { transaction, }); - await products.destroy({ + await enrollments.destroy({ transaction }); - return products; + return enrollments; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const products = await db.products.findOne( + const enrollments = await db.enrollments.findOne( { where }, { transaction }, ); - if (!products) { - return products; + if (!enrollments) { + return enrollments; } - const output = products.get({plain: true}); + const output = enrollments.get({plain: true}); @@ -281,20 +268,23 @@ module.exports = class ProductsDBApi { - output.order_items_product = await products.getOrder_items_product({ + + + + + + + output.student = await enrollments.getStudent({ transaction }); - - - - output.images = await products.getImages({ + output.course = await enrollments.getCourse({ transaction }); - output.category = await products.getCategory({ + output.certificate = await enrollments.getCertificate({ transaction }); @@ -325,15 +315,32 @@ module.exports = class ProductsDBApi { let include = [ { - model: db.categories, - as: 'category', + model: db.users, + as: 'student', - where: filter.category ? { + where: filter.student ? { [Op.or]: [ - { id: { [Op.in]: filter.category.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.student.split('|').map(term => Utils.uuid(term)) } }, { - name: { - [Op.or]: filter.category.split('|').map(term => ({ [Op.iLike]: `%${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}%` })) } }, ] @@ -345,7 +352,7 @@ module.exports = class ProductsDBApi { { model: db.file, - as: 'images', + as: 'certificate', }, ]; @@ -359,35 +366,13 @@ module.exports = class ProductsDBApi { } - if (filter.sku) { + if (filter.enrollment_code) { where = { ...where, [Op.and]: Utils.ilike( - 'products', - 'sku', - filter.sku, - ), - }; - } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'products', - 'name', - filter.name, - ), - }; - } - - if (filter.description) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'products', - 'description', - filter.description, + 'enrollments', + 'enrollment_code', + filter.enrollment_code, ), }; } @@ -397,14 +382,14 @@ module.exports = class ProductsDBApi { - if (filter.priceRange) { - const [start, end] = filter.priceRange; + if (filter.enrolled_atRange) { + const [start, end] = filter.enrolled_atRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - price: { - ...where.price, + enrolled_at: { + ...where.enrolled_at, [Op.gte]: start, }, }; @@ -413,22 +398,22 @@ module.exports = class ProductsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - price: { - ...where.price, + enrolled_at: { + ...where.enrolled_at, [Op.lte]: end, }, }; } } - if (filter.stockRange) { - const [start, end] = filter.stockRange; + if (filter.progress_percentRange) { + const [start, end] = filter.progress_percentRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - stock: { - ...where.stock, + progress_percent: { + ...where.progress_percent, [Op.gte]: start, }, }; @@ -437,8 +422,8 @@ module.exports = class ProductsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - stock: { - ...where.stock, + progress_percent: { + ...where.progress_percent, [Op.lte]: end, }, }; @@ -465,6 +450,8 @@ module.exports = class ProductsDBApi { + + if (filter.createdAtRange) { @@ -512,7 +499,7 @@ module.exports = class ProductsDBApi { } try { - const { rows, count } = await db.products.findAndCountAll(queryOptions); + const { rows, count } = await db.enrollments.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -534,25 +521,25 @@ module.exports = class ProductsDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'products', - 'name', + 'enrollments', + 'enrollment_code', query, ), ], }; } - const records = await db.products.findAll({ - attributes: [ 'id', 'name' ], + const records = await db.enrollments.findAll({ + attributes: [ 'id', 'enrollment_code' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], + orderBy: [['enrollment_code', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.name, + label: record.enrollment_code, })); } diff --git a/backend/src/db/api/orders.js b/backend/src/db/api/lessons.js similarity index 63% rename from backend/src/db/api/orders.js rename to backend/src/db/api/lessons.js index 5572dbd..47d535d 100644 --- a/backend/src/db/api/orders.js +++ b/backend/src/db/api/lessons.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class OrdersDBApi { +module.exports = class LessonsDBApi { @@ -17,48 +17,44 @@ module.exports = class OrdersDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const orders = await db.orders.create( + const lessons = await db.lessons.create( { id: data.id || undefined, - order_number: data.order_number + title: data.title || null , - status: data.status + content: data.content || null , - total: data.total + order: data.order || null , - placed_at: data.placed_at + duration: data.duration || null , - shipped_at: data.shipped_at + start_at: data.start_at || null , - delivery_date: data.delivery_date + end_at: data.end_at || null , - payment_status: data.payment_status + is_published: data.is_published || - null - , - - shipping_address: data.shipping_address - || - null + false + , importHash: data.importHash || null, @@ -69,7 +65,7 @@ module.exports = class OrdersDBApi { ); - await orders.setCustomer( data.customer || null, { + await lessons.setCourse( data.course || null, { transaction, }); @@ -77,8 +73,18 @@ module.exports = class OrdersDBApi { + await FileDBApi.replaceRelationFiles( + { + belongsTo: db.lessons.getTableName(), + belongsToColumn: 'video_files', + belongsToId: lessons.id, + }, + data.video_files, + options, + ); + - return orders; + return lessons; } @@ -87,47 +93,43 @@ module.exports = class OrdersDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const ordersData = data.map((item, index) => ({ + const lessonsData = data.map((item, index) => ({ id: item.id || undefined, - order_number: item.order_number + title: item.title || null , - status: item.status + content: item.content || null , - total: item.total + order: item.order || null , - placed_at: item.placed_at + duration: item.duration || null , - shipped_at: item.shipped_at + start_at: item.start_at || null , - delivery_date: item.delivery_date + end_at: item.end_at || null , - payment_status: item.payment_status + is_published: item.is_published || - null - , - - shipping_address: item.shipping_address - || - null + false + , importHash: item.importHash || null, @@ -137,12 +139,24 @@ module.exports = class OrdersDBApi { })); // Bulk create items - const orders = await db.orders.bulkCreate(ordersData, { transaction }); + const lessons = await db.lessons.bulkCreate(lessonsData, { 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, + ); + } + - return orders; + return lessons; } static async update(id, data, options) { @@ -150,47 +164,44 @@ module.exports = class OrdersDBApi { const transaction = (options && options.transaction) || undefined; - const orders = await db.orders.findByPk(id, {}, {transaction}); + const lessons = await db.lessons.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.order_number !== undefined) updatePayload.order_number = data.order_number; + if (data.title !== undefined) updatePayload.title = data.title; - if (data.status !== undefined) updatePayload.status = data.status; + if (data.content !== undefined) updatePayload.content = data.content; - if (data.total !== undefined) updatePayload.total = data.total; + if (data.order !== undefined) updatePayload.order = data.order; - if (data.placed_at !== undefined) updatePayload.placed_at = data.placed_at; + if (data.duration !== undefined) updatePayload.duration = data.duration; - if (data.shipped_at !== undefined) updatePayload.shipped_at = data.shipped_at; + if (data.start_at !== undefined) updatePayload.start_at = data.start_at; - if (data.delivery_date !== undefined) updatePayload.delivery_date = data.delivery_date; + if (data.end_at !== undefined) updatePayload.end_at = data.end_at; - if (data.payment_status !== undefined) updatePayload.payment_status = data.payment_status; - - - if (data.shipping_address !== undefined) updatePayload.shipping_address = data.shipping_address; + if (data.is_published !== undefined) updatePayload.is_published = data.is_published; updatePayload.updatedById = currentUser.id; - await orders.update(updatePayload, {transaction}); + await lessons.update(updatePayload, {transaction}); - if (data.customer !== undefined) { - await orders.setCustomer( + if (data.course !== undefined) { + await lessons.setCourse( - data.customer, + data.course, { transaction } ); @@ -201,15 +212,25 @@ module.exports = class OrdersDBApi { + await FileDBApi.replaceRelationFiles( + { + belongsTo: db.lessons.getTableName(), + belongsToColumn: 'video_files', + belongsToId: lessons.id, + }, + data.video_files, + options, + ); + - return orders; + return lessons; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const orders = await db.orders.findAll({ + const lessons = await db.lessons.findAll({ where: { id: { [Op.in]: ids, @@ -219,53 +240,53 @@ module.exports = class OrdersDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of orders) { + for (const record of lessons) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of orders) { + for (const record of lessons) { await record.destroy({transaction}); } }); - return orders; + return lessons; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const orders = await db.orders.findByPk(id, options); + const lessons = await db.lessons.findByPk(id, options); - await orders.update({ + await lessons.update({ deletedBy: currentUser.id }, { transaction, }); - await orders.destroy({ + await lessons.destroy({ transaction }); - return orders; + return lessons; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const orders = await db.orders.findOne( + const lessons = await db.lessons.findOne( { where }, { transaction }, ); - if (!orders) { - return orders; + if (!lessons) { + return lessons; } - const output = orders.get({plain: true}); + const output = lessons.get({plain: true}); @@ -275,23 +296,26 @@ module.exports = class OrdersDBApi { - output.order_items_order = await orders.getOrder_items_order({ + output.progress_lesson = await lessons.getProgress_lesson({ transaction }); - output.payments_order = await orders.getPayments_order({ - transaction - }); - - - output.shipments_order = await orders.getShipments_order({ + output.quizzes_lesson = await lessons.getQuizzes_lesson({ transaction }); - output.customer = await orders.getCustomer({ + + + + output.course = await lessons.getCourse({ + transaction + }); + + + output.video_files = await lessons.getVideo_files({ transaction }); @@ -322,15 +346,15 @@ module.exports = class OrdersDBApi { let include = [ { - model: db.customers, - as: 'customer', + model: db.courses, + as: 'course', - where: filter.customer ? { + where: filter.course ? { [Op.or]: [ - { id: { [Op.in]: filter.customer.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.course.split('|').map(term => Utils.uuid(term)) } }, { - name: { - [Op.or]: filter.customer.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + title: { + [Op.or]: filter.course.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -340,6 +364,11 @@ module.exports = class OrdersDBApi { + { + model: db.file, + as: 'video_files', + }, + ]; if (filter) { @@ -351,24 +380,24 @@ module.exports = class OrdersDBApi { } - if (filter.order_number) { + if (filter.title) { where = { ...where, [Op.and]: Utils.ilike( - 'orders', - 'order_number', - filter.order_number, + 'lessons', + 'title', + filter.title, ), }; } - if (filter.shipping_address) { + if (filter.content) { where = { ...where, [Op.and]: Utils.ilike( - 'orders', - 'shipping_address', - filter.shipping_address, + 'lessons', + 'content', + filter.content, ), }; } @@ -376,16 +405,34 @@ module.exports = class OrdersDBApi { + if (filter.calendarStart && filter.calendarEnd) { + where = { + ...where, + [Op.or]: [ + { + start_at: { + [Op.between]: [filter.calendarStart, filter.calendarEnd], + }, + }, + { + end_at: { + [Op.between]: [filter.calendarStart, filter.calendarEnd], + }, + }, + ], + }; + } + - if (filter.totalRange) { - const [start, end] = filter.totalRange; + if (filter.orderRange) { + const [start, end] = filter.orderRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - total: { - ...where.total, + order: { + ...where.order, [Op.gte]: start, }, }; @@ -394,22 +441,22 @@ module.exports = class OrdersDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - total: { - ...where.total, + order: { + ...where.order, [Op.lte]: end, }, }; } } - if (filter.placed_atRange) { - const [start, end] = filter.placed_atRange; + if (filter.durationRange) { + const [start, end] = filter.durationRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - placed_at: { - ...where.placed_at, + duration: { + ...where.duration, [Op.gte]: start, }, }; @@ -418,22 +465,22 @@ module.exports = class OrdersDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - placed_at: { - ...where.placed_at, + duration: { + ...where.duration, [Op.lte]: end, }, }; } } - if (filter.shipped_atRange) { - const [start, end] = filter.shipped_atRange; + if (filter.start_atRange) { + const [start, end] = filter.start_atRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - shipped_at: { - ...where.shipped_at, + start_at: { + ...where.start_at, [Op.gte]: start, }, }; @@ -442,22 +489,22 @@ module.exports = class OrdersDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - shipped_at: { - ...where.shipped_at, + start_at: { + ...where.start_at, [Op.lte]: end, }, }; } } - if (filter.delivery_dateRange) { - const [start, end] = filter.delivery_dateRange; + if (filter.end_atRange) { + const [start, end] = filter.end_atRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - delivery_date: { - ...where.delivery_date, + end_at: { + ...where.end_at, [Op.gte]: start, }, }; @@ -466,8 +513,8 @@ module.exports = class OrdersDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - delivery_date: { - ...where.delivery_date, + end_at: { + ...where.end_at, [Op.lte]: end, }, }; @@ -483,17 +530,10 @@ module.exports = class OrdersDBApi { } - if (filter.status) { + if (filter.is_published) { where = { ...where, - status: filter.status, - }; - } - - if (filter.payment_status) { - where = { - ...where, - payment_status: filter.payment_status, + is_published: filter.is_published, }; } @@ -548,7 +588,7 @@ module.exports = class OrdersDBApi { } try { - const { rows, count } = await db.orders.findAndCountAll(queryOptions); + const { rows, count } = await db.lessons.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -570,25 +610,25 @@ module.exports = class OrdersDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'orders', - 'order_number', + 'lessons', + 'title', query, ), ], }; } - const records = await db.orders.findAll({ - attributes: [ 'id', 'order_number' ], + const records = await db.lessons.findAll({ + attributes: [ 'id', 'title' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['order_number', 'ASC']], + orderBy: [['title', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.order_number, + label: record.title, })); } diff --git a/backend/src/db/api/permissions.js b/backend/src/db/api/permissions.js index 2625f43..eb9ec56 100644 --- a/backend/src/db/api/permissions.js +++ b/backend/src/db/api/permissions.js @@ -175,6 +175,8 @@ module.exports = class PermissionsDBApi { + + return output; } diff --git a/backend/src/db/api/order_items.js b/backend/src/db/api/progress.js similarity index 66% rename from backend/src/db/api/order_items.js rename to backend/src/db/api/progress.js index cefa31b..eafff88 100644 --- a/backend/src/db/api/order_items.js +++ b/backend/src/db/api/progress.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class Order_itemsDBApi { +module.exports = class ProgressDBApi { @@ -17,26 +17,32 @@ module.exports = class Order_itemsDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const order_items = await db.order_items.create( + const progress = await db.progress.create( { id: data.id || undefined, - name: data.name + summary: data.summary || null , - quantity: data.quantity + completed: data.completed + || + false + + , + + completed_at: data.completed_at || null , - unit_price: data.unit_price + percent: data.percent || null , - total_price: data.total_price + notes: data.notes || null , @@ -49,11 +55,11 @@ module.exports = class Order_itemsDBApi { ); - await order_items.setOrder( data.order || null, { + await progress.setStudent( data.student || null, { transaction, }); - await order_items.setProduct( data.product || null, { + await progress.setLesson( data.lesson || null, { transaction, }); @@ -62,7 +68,7 @@ module.exports = class Order_itemsDBApi { - return order_items; + return progress; } @@ -71,25 +77,31 @@ module.exports = class Order_itemsDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const order_itemsData = data.map((item, index) => ({ + const progressData = data.map((item, index) => ({ id: item.id || undefined, - name: item.name + summary: item.summary || null , - quantity: item.quantity + completed: item.completed + || + false + + , + + completed_at: item.completed_at || null , - unit_price: item.unit_price + percent: item.percent || null , - total_price: item.total_price + notes: item.notes || null , @@ -101,12 +113,12 @@ module.exports = class Order_itemsDBApi { })); // Bulk create items - const order_items = await db.order_items.bulkCreate(order_itemsData, { transaction }); + const progress = await db.progress.bulkCreate(progressData, { transaction }); // For each item created, replace relation files - return order_items; + return progress; } static async update(id, data, options) { @@ -114,44 +126,47 @@ module.exports = class Order_itemsDBApi { const transaction = (options && options.transaction) || undefined; - const order_items = await db.order_items.findByPk(id, {}, {transaction}); + const progress = await db.progress.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.name !== undefined) updatePayload.name = data.name; + if (data.summary !== undefined) updatePayload.summary = data.summary; - if (data.quantity !== undefined) updatePayload.quantity = data.quantity; + if (data.completed !== undefined) updatePayload.completed = data.completed; - if (data.unit_price !== undefined) updatePayload.unit_price = data.unit_price; + if (data.completed_at !== undefined) updatePayload.completed_at = data.completed_at; - if (data.total_price !== undefined) updatePayload.total_price = data.total_price; + if (data.percent !== undefined) updatePayload.percent = data.percent; + + + if (data.notes !== undefined) updatePayload.notes = data.notes; updatePayload.updatedById = currentUser.id; - await order_items.update(updatePayload, {transaction}); + await progress.update(updatePayload, {transaction}); - if (data.order !== undefined) { - await order_items.setOrder( + if (data.student !== undefined) { + await progress.setStudent( - data.order, + data.student, { transaction } ); } - if (data.product !== undefined) { - await order_items.setProduct( + if (data.lesson !== undefined) { + await progress.setLesson( - data.product, + data.lesson, { transaction } ); @@ -163,14 +178,14 @@ module.exports = class Order_itemsDBApi { - return order_items; + return progress; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const order_items = await db.order_items.findAll({ + const progress = await db.progress.findAll({ where: { id: { [Op.in]: ids, @@ -180,53 +195,53 @@ module.exports = class Order_itemsDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of order_items) { + for (const record of progress) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of order_items) { + for (const record of progress) { await record.destroy({transaction}); } }); - return order_items; + return progress; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const order_items = await db.order_items.findByPk(id, options); + const progress = await db.progress.findByPk(id, options); - await order_items.update({ + await progress.update({ deletedBy: currentUser.id }, { transaction, }); - await order_items.destroy({ + await progress.destroy({ transaction }); - return order_items; + return progress; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const order_items = await db.order_items.findOne( + const progress = await db.progress.findOne( { where }, { transaction }, ); - if (!order_items) { - return order_items; + if (!progress) { + return progress; } - const output = order_items.get({plain: true}); + const output = progress.get({plain: true}); @@ -240,12 +255,14 @@ module.exports = class Order_itemsDBApi { - output.order = await order_items.getOrder({ + + + output.student = await progress.getStudent({ transaction }); - output.product = await order_items.getProduct({ + output.lesson = await progress.getLesson({ transaction }); @@ -276,15 +293,15 @@ module.exports = class Order_itemsDBApi { let include = [ { - model: db.orders, - as: 'order', + model: db.users, + as: 'student', - where: filter.order ? { + where: filter.student ? { [Op.or]: [ - { id: { [Op.in]: filter.order.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.student.split('|').map(term => Utils.uuid(term)) } }, { - order_number: { - [Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + firstName: { + [Op.or]: filter.student.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -293,15 +310,15 @@ module.exports = class Order_itemsDBApi { }, { - model: db.products, - as: 'product', + model: db.lessons, + as: 'lesson', - where: filter.product ? { + where: filter.lesson ? { [Op.or]: [ - { id: { [Op.in]: filter.product.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.lesson.split('|').map(term => Utils.uuid(term)) } }, { - name: { - [Op.or]: filter.product.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + title: { + [Op.or]: filter.lesson.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -322,13 +339,24 @@ module.exports = class Order_itemsDBApi { } - if (filter.name) { + if (filter.summary) { where = { ...where, [Op.and]: Utils.ilike( - 'order_items', - 'name', - filter.name, + 'progress', + 'summary', + filter.summary, + ), + }; + } + + if (filter.notes) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'progress', + 'notes', + filter.notes, ), }; } @@ -338,14 +366,14 @@ module.exports = class Order_itemsDBApi { - if (filter.quantityRange) { - const [start, end] = filter.quantityRange; + if (filter.completed_atRange) { + const [start, end] = filter.completed_atRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - quantity: { - ...where.quantity, + completed_at: { + ...where.completed_at, [Op.gte]: start, }, }; @@ -354,22 +382,22 @@ module.exports = class Order_itemsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - quantity: { - ...where.quantity, + completed_at: { + ...where.completed_at, [Op.lte]: end, }, }; } } - if (filter.unit_priceRange) { - const [start, end] = filter.unit_priceRange; + if (filter.percentRange) { + const [start, end] = filter.percentRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - unit_price: { - ...where.unit_price, + percent: { + ...where.percent, [Op.gte]: start, }, }; @@ -378,32 +406,8 @@ module.exports = class Order_itemsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - unit_price: { - ...where.unit_price, - [Op.lte]: end, - }, - }; - } - } - - if (filter.total_priceRange) { - const [start, end] = filter.total_priceRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - total_price: { - ...where.total_price, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - total_price: { - ...where.total_price, + percent: { + ...where.percent, [Op.lte]: end, }, }; @@ -419,6 +423,13 @@ module.exports = class Order_itemsDBApi { } + if (filter.completed) { + where = { + ...where, + completed: filter.completed, + }; + } + @@ -472,7 +483,7 @@ module.exports = class Order_itemsDBApi { } try { - const { rows, count } = await db.order_items.findAndCountAll(queryOptions); + const { rows, count } = await db.progress.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -494,25 +505,25 @@ module.exports = class Order_itemsDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'order_items', - 'name', + 'progress', + 'summary', query, ), ], }; } - const records = await db.order_items.findAll({ - attributes: [ 'id', 'name' ], + const records = await db.progress.findAll({ + attributes: [ 'id', 'summary' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], + orderBy: [['summary', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.name, + label: record.summary, })); } diff --git a/backend/src/db/api/customers.js b/backend/src/db/api/quiz_questions.js similarity index 64% rename from backend/src/db/api/customers.js rename to backend/src/db/api/quiz_questions.js index ef581aa..068e414 100644 --- a/backend/src/db/api/customers.js +++ b/backend/src/db/api/quiz_questions.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class CustomersDBApi { +module.exports = class Quiz_questionsDBApi { @@ -17,42 +17,26 @@ module.exports = class CustomersDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const customers = await db.customers.create( + const quiz_questions = await db.quiz_questions.create( { id: data.id || undefined, - name: data.name + question: data.question || null , - email: data.email + question_type: data.question_type || null , - phone: data.phone + choices: data.choices || null , - address: data.address - || - null - , - - notes: data.notes - || - null - , - - vip: data.vip - || - false - - , - - tax_number: data.tax_number + answer: data.answer || null , @@ -65,12 +49,16 @@ module.exports = class CustomersDBApi { ); + await quiz_questions.setQuiz( data.quiz || null, { + transaction, + }); + - return customers; + return quiz_questions; } @@ -79,41 +67,25 @@ module.exports = class CustomersDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const customersData = data.map((item, index) => ({ + const quiz_questionsData = data.map((item, index) => ({ id: item.id || undefined, - name: item.name + question: item.question || null , - email: item.email + question_type: item.question_type || null , - phone: item.phone + choices: item.choices || null , - address: item.address - || - null - , - - notes: item.notes - || - null - , - - vip: item.vip - || - false - - , - - tax_number: item.tax_number + answer: item.answer || null , @@ -125,12 +97,12 @@ module.exports = class CustomersDBApi { })); // Bulk create items - const customers = await db.customers.bulkCreate(customersData, { transaction }); + const quiz_questions = await db.quiz_questions.bulkCreate(quiz_questionsData, { transaction }); // For each item created, replace relation files - return customers; + return quiz_questions; } static async update(id, data, options) { @@ -138,54 +110,54 @@ module.exports = class CustomersDBApi { const transaction = (options && options.transaction) || undefined; - const customers = await db.customers.findByPk(id, {}, {transaction}); + const quiz_questions = await db.quiz_questions.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.name !== undefined) updatePayload.name = data.name; + if (data.question !== undefined) updatePayload.question = data.question; - if (data.email !== undefined) updatePayload.email = data.email; + if (data.question_type !== undefined) updatePayload.question_type = data.question_type; - if (data.phone !== undefined) updatePayload.phone = data.phone; + if (data.choices !== undefined) updatePayload.choices = data.choices; - if (data.address !== undefined) updatePayload.address = data.address; - - - if (data.notes !== undefined) updatePayload.notes = data.notes; - - - if (data.vip !== undefined) updatePayload.vip = data.vip; - - - if (data.tax_number !== undefined) updatePayload.tax_number = data.tax_number; + if (data.answer !== undefined) updatePayload.answer = data.answer; updatePayload.updatedById = currentUser.id; - await customers.update(updatePayload, {transaction}); + await quiz_questions.update(updatePayload, {transaction}); + if (data.quiz !== undefined) { + await quiz_questions.setQuiz( + + data.quiz, + + { transaction } + ); + } + - return customers; + return quiz_questions; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const customers = await db.customers.findAll({ + const quiz_questions = await db.quiz_questions.findAll({ where: { id: { [Op.in]: ids, @@ -195,53 +167,53 @@ module.exports = class CustomersDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of customers) { + for (const record of quiz_questions) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of customers) { + for (const record of quiz_questions) { await record.destroy({transaction}); } }); - return customers; + return quiz_questions; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const customers = await db.customers.findByPk(id, options); + const quiz_questions = await db.quiz_questions.findByPk(id, options); - await customers.update({ + await quiz_questions.update({ deletedBy: currentUser.id }, { transaction, }); - await customers.destroy({ + await quiz_questions.destroy({ transaction }); - return customers; + return quiz_questions; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const customers = await db.customers.findOne( + const quiz_questions = await db.quiz_questions.findOne( { where }, { transaction }, ); - if (!customers) { - return customers; + if (!quiz_questions) { + return quiz_questions; } - const output = customers.get({plain: true}); + const output = quiz_questions.get({plain: true}); @@ -250,15 +222,18 @@ module.exports = class CustomersDBApi { - output.orders_customer = await customers.getOrders_customer({ + + + + + + + + output.quiz = await quiz_questions.getQuiz({ transaction }); - - - - return output; } @@ -284,6 +259,23 @@ module.exports = class CustomersDBApi { let include = [ + { + model: db.quizzes, + as: 'quiz', + + where: filter.quiz ? { + [Op.or]: [ + { id: { [Op.in]: filter.quiz.split('|').map(term => Utils.uuid(term)) } }, + { + title: { + [Op.or]: filter.quiz.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + } + }, + ] + } : {}, + + }, + ]; @@ -297,68 +289,35 @@ module.exports = class CustomersDBApi { } - if (filter.name) { + if (filter.question) { where = { ...where, [Op.and]: Utils.ilike( - 'customers', - 'name', - filter.name, + 'quiz_questions', + 'question', + filter.question, ), }; } - if (filter.email) { + if (filter.choices) { where = { ...where, [Op.and]: Utils.ilike( - 'customers', - 'email', - filter.email, + 'quiz_questions', + 'choices', + filter.choices, ), }; } - if (filter.phone) { + if (filter.answer) { where = { ...where, [Op.and]: Utils.ilike( - 'customers', - 'phone', - filter.phone, - ), - }; - } - - if (filter.address) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'customers', - 'address', - filter.address, - ), - }; - } - - if (filter.notes) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'customers', - 'notes', - filter.notes, - ), - }; - } - - if (filter.tax_number) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'customers', - 'tax_number', - filter.tax_number, + 'quiz_questions', + 'answer', + filter.answer, ), }; } @@ -377,15 +336,17 @@ module.exports = class CustomersDBApi { } - if (filter.vip) { + if (filter.question_type) { where = { ...where, - vip: filter.vip, + question_type: filter.question_type, }; } + + if (filter.createdAtRange) { @@ -433,7 +394,7 @@ module.exports = class CustomersDBApi { } try { - const { rows, count } = await db.customers.findAndCountAll(queryOptions); + const { rows, count } = await db.quiz_questions.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -455,25 +416,25 @@ module.exports = class CustomersDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'customers', - 'name', + 'quiz_questions', + 'question', query, ), ], }; } - const records = await db.customers.findAll({ - attributes: [ 'id', 'name' ], + const records = await db.quiz_questions.findAll({ + attributes: [ 'id', 'question' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], + orderBy: [['question', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.name, + label: record.question, })); } diff --git a/backend/src/db/api/payments.js b/backend/src/db/api/quizzes.js similarity index 69% rename from backend/src/db/api/payments.js rename to backend/src/db/api/quizzes.js index 86d39da..33fb80b 100644 --- a/backend/src/db/api/payments.js +++ b/backend/src/db/api/quizzes.js @@ -9,7 +9,7 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class PaymentsDBApi { +module.exports = class QuizzesDBApi { @@ -17,33 +17,34 @@ module.exports = class PaymentsDBApi { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const payments = await db.payments.create( + const quizzes = await db.quizzes.create( { id: data.id || undefined, - reference: data.reference + title: data.title || null , - amount: data.amount + description: data.description || null , - method: data.method + passing_score: data.passing_score || null , - status: data.status + time_limit: data.time_limit || null , - paid_at: data.paid_at + is_active: data.is_active || - null + false + , importHash: data.importHash || null, @@ -54,7 +55,7 @@ module.exports = class PaymentsDBApi { ); - await payments.setOrder( data.order || null, { + await quizzes.setLesson( data.lesson || null, { transaction, }); @@ -63,7 +64,7 @@ module.exports = class PaymentsDBApi { - return payments; + return quizzes; } @@ -72,32 +73,33 @@ module.exports = class PaymentsDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const paymentsData = data.map((item, index) => ({ + const quizzesData = data.map((item, index) => ({ id: item.id || undefined, - reference: item.reference + title: item.title || null , - amount: item.amount + description: item.description || null , - method: item.method + passing_score: item.passing_score || null , - status: item.status + time_limit: item.time_limit || null , - paid_at: item.paid_at + is_active: item.is_active || - null + false + , importHash: item.importHash || null, @@ -107,12 +109,12 @@ module.exports = class PaymentsDBApi { })); // Bulk create items - const payments = await db.payments.bulkCreate(paymentsData, { transaction }); + const quizzes = await db.quizzes.bulkCreate(quizzesData, { transaction }); // For each item created, replace relation files - return payments; + return quizzes; } static async update(id, data, options) { @@ -120,38 +122,38 @@ module.exports = class PaymentsDBApi { const transaction = (options && options.transaction) || undefined; - const payments = await db.payments.findByPk(id, {}, {transaction}); + const quizzes = await db.quizzes.findByPk(id, {}, {transaction}); const updatePayload = {}; - if (data.reference !== undefined) updatePayload.reference = data.reference; + if (data.title !== undefined) updatePayload.title = data.title; - if (data.amount !== undefined) updatePayload.amount = data.amount; + if (data.description !== undefined) updatePayload.description = data.description; - if (data.method !== undefined) updatePayload.method = data.method; + if (data.passing_score !== undefined) updatePayload.passing_score = data.passing_score; - if (data.status !== undefined) updatePayload.status = data.status; + if (data.time_limit !== undefined) updatePayload.time_limit = data.time_limit; - if (data.paid_at !== undefined) updatePayload.paid_at = data.paid_at; + if (data.is_active !== undefined) updatePayload.is_active = data.is_active; updatePayload.updatedById = currentUser.id; - await payments.update(updatePayload, {transaction}); + await quizzes.update(updatePayload, {transaction}); - if (data.order !== undefined) { - await payments.setOrder( + if (data.lesson !== undefined) { + await quizzes.setLesson( - data.order, + data.lesson, { transaction } ); @@ -163,14 +165,14 @@ module.exports = class PaymentsDBApi { - return payments; + return quizzes; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const payments = await db.payments.findAll({ + const quizzes = await db.quizzes.findAll({ where: { id: { [Op.in]: ids, @@ -180,53 +182,53 @@ module.exports = class PaymentsDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of payments) { + for (const record of quizzes) { await record.update( {deletedBy: currentUser.id}, {transaction} ); } - for (const record of payments) { + for (const record of quizzes) { await record.destroy({transaction}); } }); - return payments; + return quizzes; } static async remove(id, options) { const currentUser = (options && options.currentUser) || {id: null}; const transaction = (options && options.transaction) || undefined; - const payments = await db.payments.findByPk(id, options); + const quizzes = await db.quizzes.findByPk(id, options); - await payments.update({ + await quizzes.update({ deletedBy: currentUser.id }, { transaction, }); - await payments.destroy({ + await quizzes.destroy({ transaction }); - return payments; + return quizzes; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const payments = await db.payments.findOne( + const quizzes = await db.quizzes.findOne( { where }, { transaction }, ); - if (!payments) { - return payments; + if (!quizzes) { + return quizzes; } - const output = payments.get({plain: true}); + const output = quizzes.get({plain: true}); @@ -238,9 +240,15 @@ module.exports = class PaymentsDBApi { + output.quiz_questions_quiz = await quizzes.getQuiz_questions_quiz({ + transaction + }); - output.order = await payments.getOrder({ + + + + output.lesson = await quizzes.getLesson({ transaction }); @@ -271,15 +279,15 @@ module.exports = class PaymentsDBApi { let include = [ { - model: db.orders, - as: 'order', + model: db.lessons, + as: 'lesson', - where: filter.order ? { + where: filter.lesson ? { [Op.or]: [ - { id: { [Op.in]: filter.order.split('|').map(term => Utils.uuid(term)) } }, + { id: { [Op.in]: filter.lesson.split('|').map(term => Utils.uuid(term)) } }, { - order_number: { - [Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) + title: { + [Op.or]: filter.lesson.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) } }, ] @@ -300,13 +308,24 @@ module.exports = class PaymentsDBApi { } - if (filter.reference) { + if (filter.title) { where = { ...where, [Op.and]: Utils.ilike( - 'payments', - 'reference', - filter.reference, + 'quizzes', + 'title', + filter.title, + ), + }; + } + + if (filter.description) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'quizzes', + 'description', + filter.description, ), }; } @@ -316,14 +335,14 @@ module.exports = class PaymentsDBApi { - if (filter.amountRange) { - const [start, end] = filter.amountRange; + if (filter.passing_scoreRange) { + const [start, end] = filter.passing_scoreRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - amount: { - ...where.amount, + passing_score: { + ...where.passing_score, [Op.gte]: start, }, }; @@ -332,22 +351,22 @@ module.exports = class PaymentsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - amount: { - ...where.amount, + passing_score: { + ...where.passing_score, [Op.lte]: end, }, }; } } - if (filter.paid_atRange) { - const [start, end] = filter.paid_atRange; + if (filter.time_limitRange) { + const [start, end] = filter.time_limitRange; if (start !== undefined && start !== null && start !== '') { where = { ...where, - paid_at: { - ...where.paid_at, + time_limit: { + ...where.time_limit, [Op.gte]: start, }, }; @@ -356,8 +375,8 @@ module.exports = class PaymentsDBApi { if (end !== undefined && end !== null && end !== '') { where = { ...where, - paid_at: { - ...where.paid_at, + time_limit: { + ...where.time_limit, [Op.lte]: end, }, }; @@ -373,17 +392,10 @@ module.exports = class PaymentsDBApi { } - if (filter.method) { + if (filter.is_active) { where = { ...where, - method: filter.method, - }; - } - - if (filter.status) { - where = { - ...where, - status: filter.status, + is_active: filter.is_active, }; } @@ -438,7 +450,7 @@ module.exports = class PaymentsDBApi { } try { - const { rows, count } = await db.payments.findAndCountAll(queryOptions); + const { rows, count } = await db.quizzes.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -460,25 +472,25 @@ module.exports = class PaymentsDBApi { [Op.or]: [ { ['id']: Utils.uuid(query) }, Utils.ilike( - 'payments', - 'reference', + 'quizzes', + 'title', query, ), ], }; } - const records = await db.payments.findAll({ - attributes: [ 'id', 'reference' ], + const records = await db.quizzes.findAll({ + attributes: [ 'id', 'title' ], where, limit: limit ? Number(limit) : undefined, offset: offset ? Number(offset) : undefined, - orderBy: [['reference', 'ASC']], + orderBy: [['title', 'ASC']], }); return records.map((record) => ({ id: record.id, - label: record.reference, + label: record.title, })); } diff --git a/backend/src/db/api/roles.js b/backend/src/db/api/roles.js index 9c68b9e..6cfdaf0 100644 --- a/backend/src/db/api/roles.js +++ b/backend/src/db/api/roles.js @@ -200,6 +200,8 @@ 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 c6a1b5e..3d95e52 100644 --- a/backend/src/db/api/users.js +++ b/backend/src/db/api/users.js @@ -404,11 +404,29 @@ module.exports = class UsersDBApi { + output.courses_instructor = await users.getCourses_instructor({ + transaction + }); + + + + output.enrollments_student = await users.getEnrollments_student({ + transaction + }); + + + output.progress_student = await users.getProgress_student({ + transaction + }); + output.certificates_student = await users.getCertificates_student({ + transaction + }); + output.avatar = await users.getAvatar({ diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js index ba70dd7..1602da1 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_store_operations_manager', + database: 'db_courseflow_lms', host: process.env.DB_HOST || 'localhost', logging: console.log, seederStorage: 'sequelize', diff --git a/backend/src/db/migrations/1768387771195.js b/backend/src/db/migrations/1768396909316.js similarity index 72% rename from backend/src/db/migrations/1768387771195.js rename to backend/src/db/migrations/1768396909316.js index ebf0a74..5183529 100644 --- a/backend/src/db/migrations/1768387771195.js +++ b/backend/src/db/migrations/1768396909316.js @@ -108,7 +108,7 @@ module.exports = { - await queryInterface.createTable('products', { + await queryInterface.createTable('course_categories', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -140,7 +140,7 @@ module.exports = { - await queryInterface.createTable('categories', { + await queryInterface.createTable('courses', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -172,7 +172,7 @@ module.exports = { - await queryInterface.createTable('customers', { + await queryInterface.createTable('lessons', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -204,7 +204,7 @@ module.exports = { - await queryInterface.createTable('orders', { + await queryInterface.createTable('enrollments', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -236,7 +236,7 @@ module.exports = { - await queryInterface.createTable('order_items', { + await queryInterface.createTable('progress', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -268,7 +268,7 @@ module.exports = { - await queryInterface.createTable('payments', { + await queryInterface.createTable('quizzes', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -300,7 +300,71 @@ module.exports = { - await queryInterface.createTable('shipments', { + await queryInterface.createTable('quiz_questions', { + 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('announcements', { + 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('certificates', { id: { type: Sequelize.DataTypes.UUID, defaultValue: Sequelize.DataTypes.UUIDV4, @@ -591,22 +655,7 @@ module.exports = { await queryInterface.addColumn( - 'products', - 'sku', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'products', + 'course_categories', 'name', { type: Sequelize.DataTypes.TEXT, @@ -621,7 +670,22 @@ module.exports = { await queryInterface.addColumn( - 'products', + 'course_categories', + 'slug', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'course_categories', 'description', { type: Sequelize.DataTypes.TEXT, @@ -636,7 +700,124 @@ module.exports = { await queryInterface.addColumn( - 'products', + 'courses', + 'title', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'courses', + 'short_description', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'courses', + 'description', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'courses', + 'instructorId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'users', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'courses', + 'categoryId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'course_categories', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'courses', + 'level', + { + type: Sequelize.DataTypes.ENUM, + + + values: ['Beginner','Intermediate','Advanced'], + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'courses', + 'language', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'courses', 'price', { type: Sequelize.DataTypes.DECIMAL, @@ -651,187 +832,8 @@ module.exports = { await queryInterface.addColumn( - 'products', - 'stock', - { - type: Sequelize.DataTypes.INTEGER, - - - - }, - { transaction } - ); - - - - - - - await queryInterface.addColumn( - 'products', - 'categoryId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'categories', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'products', - 'status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['active','inactive','archived'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'categories', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'categories', - 'description', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'categories', - 'parentId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'categories', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'customers', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'customers', - 'email', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'customers', - 'phone', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'customers', - 'address', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'customers', - 'notes', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'customers', - 'vip', + 'courses', + 'published', { type: Sequelize.DataTypes.BOOLEAN, @@ -848,90 +850,8 @@ module.exports = { await queryInterface.addColumn( - 'customers', - 'tax_number', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'orders', - 'order_number', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'orders', - 'customerId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'customers', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'orders', - 'status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['Pending','Processing','Shipped','Delivered','Cancelled','Returned','Refunded'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'orders', - 'total', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'orders', - 'placed_at', + 'courses', + 'published_at', { type: Sequelize.DataTypes.DATE, @@ -943,127 +863,12 @@ module.exports = { - - await queryInterface.addColumn( - 'orders', - 'shipped_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - await queryInterface.addColumn( - 'orders', - 'delivery_date', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'orders', - 'payment_status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['Pending','Paid','Failed','Refunded'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'orders', - 'shipping_address', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'order_items', - 'name', - { - type: Sequelize.DataTypes.TEXT, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'order_items', - 'orderId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'orders', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'order_items', - 'productId', - { - type: Sequelize.DataTypes.UUID, - - - - references: { - model: 'products', - key: 'id', - }, - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'order_items', - 'quantity', + 'courses', + 'duration', { type: Sequelize.DataTypes.INTEGER, @@ -1077,38 +882,8 @@ module.exports = { await queryInterface.addColumn( - 'order_items', - 'unit_price', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'order_items', - 'total_price', - { - type: Sequelize.DataTypes.DECIMAL, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'payments', - 'reference', + 'lessons', + 'title', { type: Sequelize.DataTypes.TEXT, @@ -1122,15 +897,15 @@ module.exports = { await queryInterface.addColumn( - 'payments', - 'orderId', + 'lessons', + 'courseId', { type: Sequelize.DataTypes.UUID, references: { - model: 'orders', + model: 'courses', key: 'id', }, @@ -1142,8 +917,295 @@ module.exports = { await queryInterface.addColumn( - 'payments', - 'amount', + 'lessons', + 'content', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + + + await queryInterface.addColumn( + 'lessons', + 'order', + { + type: Sequelize.DataTypes.INTEGER, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'lessons', + 'duration', + { + type: Sequelize.DataTypes.INTEGER, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'lessons', + 'start_at', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'lessons', + 'end_at', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'lessons', + 'is_published', + { + type: Sequelize.DataTypes.BOOLEAN, + + defaultValue: false, + allowNull: false, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'enrollments', + 'enrollment_code', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'enrollments', + 'studentId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'users', + key: 'id', + }, + + }, + { transaction } + ); + + + + + 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: ['pending','active','completed','dropped'], + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'enrollments', + 'progress_percent', + { + type: Sequelize.DataTypes.DECIMAL, + + + + }, + { transaction } + ); + + + + + + + await queryInterface.addColumn( + 'progress', + 'summary', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'progress', + 'studentId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'users', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'progress', + 'lessonId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'lessons', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'progress', + 'completed', + { + type: Sequelize.DataTypes.BOOLEAN, + + defaultValue: false, + allowNull: false, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'progress', + 'completed_at', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'progress', + 'percent', { type: Sequelize.DataTypes.DECIMAL, @@ -1157,44 +1219,10 @@ module.exports = { await queryInterface.addColumn( - 'payments', - 'method', + 'progress', + 'notes', { - type: Sequelize.DataTypes.ENUM, - - - values: ['Card','PayPal','BankTransfer','Cash'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'payments', - 'status', - { - type: Sequelize.DataTypes.ENUM, - - - values: ['Pending','Completed','Failed','Refunded'], - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'payments', - 'paid_at', - { - type: Sequelize.DataTypes.DATE, + type: Sequelize.DataTypes.TEXT, @@ -1206,15 +1234,30 @@ module.exports = { await queryInterface.addColumn( - 'shipments', - 'orderId', + 'quizzes', + 'title', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'quizzes', + 'lessonId', { type: Sequelize.DataTypes.UUID, references: { - model: 'orders', + model: 'lessons', key: 'id', }, @@ -1226,8 +1269,8 @@ module.exports = { await queryInterface.addColumn( - 'shipments', - 'carrier', + 'quizzes', + 'description', { type: Sequelize.DataTypes.TEXT, @@ -1241,8 +1284,76 @@ module.exports = { await queryInterface.addColumn( - 'shipments', - 'tracking_number', + 'quizzes', + 'passing_score', + { + type: Sequelize.DataTypes.INTEGER, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'quizzes', + 'time_limit', + { + type: Sequelize.DataTypes.INTEGER, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'quizzes', + 'is_active', + { + type: Sequelize.DataTypes.BOOLEAN, + + defaultValue: false, + allowNull: false, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'quiz_questions', + 'quizId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'quizzes', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'quiz_questions', + 'question', { type: Sequelize.DataTypes.TEXT, @@ -1256,43 +1367,13 @@ module.exports = { await queryInterface.addColumn( - 'shipments', - 'shipped_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'shipments', - 'delivered_at', - { - type: Sequelize.DataTypes.DATE, - - - - }, - { transaction } - ); - - - - - await queryInterface.addColumn( - 'shipments', - 'status', + 'quiz_questions', + 'question_type', { type: Sequelize.DataTypes.ENUM, - values: ['Pending','InTransit','Delivered','Returned'], + values: ['multiple_choice','true_false','short_answer'], }, @@ -1300,6 +1381,191 @@ module.exports = { ); + + + await queryInterface.addColumn( + 'quiz_questions', + 'choices', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'quiz_questions', + 'answer', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'announcements', + 'courseId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'courses', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'announcements', + 'title', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'announcements', + 'content', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'announcements', + 'published_at', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'announcements', + 'is_active', + { + type: Sequelize.DataTypes.BOOLEAN, + + defaultValue: false, + allowNull: false, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'certificates', + 'serial', + { + type: Sequelize.DataTypes.TEXT, + + + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'certificates', + 'studentId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'users', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'certificates', + 'courseId', + { + type: Sequelize.DataTypes.UUID, + + + + references: { + model: 'courses', + key: 'id', + }, + + }, + { transaction } + ); + + + + + await queryInterface.addColumn( + 'certificates', + 'issued_at', + { + type: Sequelize.DataTypes.DATE, + + + + }, + { transaction } + ); + + + + await transaction.commit(); @@ -1321,240 +1587,170 @@ module.exports = { try { + + await queryInterface.removeColumn( - 'shipments', - 'status', + 'certificates', + 'issued_at', { transaction } ); await queryInterface.removeColumn( - 'shipments', - 'delivered_at', + 'certificates', + 'courseId', { transaction } ); await queryInterface.removeColumn( - 'shipments', - 'shipped_at', + 'certificates', + 'studentId', { transaction } ); await queryInterface.removeColumn( - 'shipments', - 'tracking_number', + 'certificates', + 'serial', { transaction } ); await queryInterface.removeColumn( - 'shipments', - 'carrier', + 'announcements', + 'is_active', { transaction } ); await queryInterface.removeColumn( - 'shipments', - 'orderId', + 'announcements', + 'published_at', { transaction } ); await queryInterface.removeColumn( - 'payments', - 'paid_at', + 'announcements', + 'content', { transaction } ); await queryInterface.removeColumn( - 'payments', - 'status', + 'announcements', + 'title', { transaction } ); await queryInterface.removeColumn( - 'payments', - 'method', + 'announcements', + 'courseId', { transaction } ); await queryInterface.removeColumn( - 'payments', - 'amount', + 'quiz_questions', + 'answer', { transaction } ); await queryInterface.removeColumn( - 'payments', - 'orderId', + 'quiz_questions', + 'choices', { transaction } ); await queryInterface.removeColumn( - 'payments', - 'reference', + 'quiz_questions', + 'question_type', { transaction } ); await queryInterface.removeColumn( - 'order_items', - 'total_price', + 'quiz_questions', + 'question', { transaction } ); await queryInterface.removeColumn( - 'order_items', - 'unit_price', + 'quiz_questions', + 'quizId', { transaction } ); await queryInterface.removeColumn( - 'order_items', - 'quantity', + 'quizzes', + 'is_active', { transaction } ); await queryInterface.removeColumn( - 'order_items', - 'productId', + 'quizzes', + 'time_limit', { transaction } ); await queryInterface.removeColumn( - 'order_items', - 'orderId', + 'quizzes', + 'passing_score', { transaction } ); await queryInterface.removeColumn( - 'order_items', - 'name', + 'quizzes', + 'description', { transaction } ); await queryInterface.removeColumn( - 'orders', - 'shipping_address', + 'quizzes', + 'lessonId', { transaction } ); await queryInterface.removeColumn( - 'orders', - 'payment_status', + 'quizzes', + 'title', { transaction } ); await queryInterface.removeColumn( - 'orders', - 'delivery_date', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'orders', - 'shipped_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'orders', - 'placed_at', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'orders', - 'total', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'orders', - 'status', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'orders', - 'customerId', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'orders', - 'order_number', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'customers', - 'tax_number', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'customers', - 'vip', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'customers', + 'progress', 'notes', { transaction } ); @@ -1562,63 +1758,65 @@ module.exports = { await queryInterface.removeColumn( - 'customers', - 'address', + 'progress', + 'percent', { transaction } ); await queryInterface.removeColumn( - 'customers', - 'phone', + 'progress', + 'completed_at', { transaction } ); await queryInterface.removeColumn( - 'customers', - 'email', + 'progress', + 'completed', { transaction } ); await queryInterface.removeColumn( - 'customers', - 'name', + 'progress', + 'lessonId', { transaction } ); await queryInterface.removeColumn( - 'categories', - 'parentId', + 'progress', + 'studentId', { transaction } ); await queryInterface.removeColumn( - 'categories', - 'description', + 'progress', + 'summary', + { transaction } + ); + + + + + + await queryInterface.removeColumn( + 'enrollments', + 'progress_percent', { transaction } ); await queryInterface.removeColumn( - 'categories', - 'name', - { transaction } - ); - - - - await queryInterface.removeColumn( - 'products', + 'enrollments', 'status', { transaction } ); @@ -1626,8 +1824,72 @@ module.exports = { await queryInterface.removeColumn( - 'products', - 'categoryId', + 'enrollments', + 'enrolled_at', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'enrollments', + 'courseId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'enrollments', + 'studentId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'enrollments', + 'enrollment_code', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'lessons', + 'is_published', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'lessons', + 'end_at', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'lessons', + 'start_at', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'lessons', + 'duration', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'lessons', + 'order', { transaction } ); @@ -1636,15 +1898,57 @@ module.exports = { await queryInterface.removeColumn( - 'products', - 'stock', + 'lessons', + 'content', { transaction } ); await queryInterface.removeColumn( - 'products', + 'lessons', + 'courseId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'lessons', + 'title', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'courses', + 'duration', + { transaction } + ); + + + + + + await queryInterface.removeColumn( + 'courses', + 'published_at', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'courses', + 'published', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'courses', 'price', { transaction } ); @@ -1652,7 +1956,39 @@ module.exports = { await queryInterface.removeColumn( - 'products', + 'courses', + 'language', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'courses', + 'level', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'courses', + 'categoryId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'courses', + 'instructorId', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'courses', 'description', { transaction } ); @@ -1660,16 +1996,40 @@ module.exports = { await queryInterface.removeColumn( - 'products', - 'name', + 'courses', + 'short_description', { transaction } ); await queryInterface.removeColumn( - 'products', - 'sku', + 'courses', + 'title', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'course_categories', + 'description', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'course_categories', + 'slug', + { transaction } + ); + + + + await queryInterface.removeColumn( + 'course_categories', + 'name', { transaction } ); @@ -1809,31 +2169,39 @@ module.exports = { - await queryInterface.dropTable('shipments', { transaction }); + await queryInterface.dropTable('certificates', { transaction }); - await queryInterface.dropTable('payments', { transaction }); + await queryInterface.dropTable('announcements', { transaction }); - await queryInterface.dropTable('order_items', { transaction }); + await queryInterface.dropTable('quiz_questions', { transaction }); - await queryInterface.dropTable('orders', { transaction }); + await queryInterface.dropTable('quizzes', { transaction }); - await queryInterface.dropTable('customers', { transaction }); + await queryInterface.dropTable('progress', { transaction }); - await queryInterface.dropTable('categories', { transaction }); + await queryInterface.dropTable('enrollments', { transaction }); - await queryInterface.dropTable('products', { transaction }); + await queryInterface.dropTable('lessons', { transaction }); + + + + await queryInterface.dropTable('courses', { transaction }); + + + + await queryInterface.dropTable('course_categories', { transaction }); diff --git a/backend/src/db/models/payments.js b/backend/src/db/models/announcements.js similarity index 63% rename from backend/src/db/models/payments.js rename to backend/src/db/models/announcements.js index b590bc6..28e9961 100644 --- a/backend/src/db/models/payments.js +++ b/backend/src/db/models/announcements.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const payments = sequelize.define( - 'payments', + const announcements = sequelize.define( + 'announcements', { id: { type: DataTypes.UUID, @@ -14,69 +14,35 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -reference: { +title: { type: DataTypes.TEXT, }, -amount: { - type: DataTypes.DECIMAL, +content: { + type: DataTypes.TEXT, }, -method: { - type: DataTypes.ENUM, - - - - values: [ - -"Card", - - -"PayPal", - - -"BankTransfer", - - -"Cash" - - ], - - }, - -status: { - type: DataTypes.ENUM, - - - - values: [ - -"Pending", - - -"Completed", - - -"Failed", - - -"Refunded" - - ], - - }, - -paid_at: { +published_at: { type: DataTypes.DATE, + }, + +is_active: { + type: DataTypes.BOOLEAN, + + allowNull: false, + defaultValue: false, + + + }, importHash: { @@ -92,7 +58,7 @@ paid_at: { }, ); - payments.associate = (db) => { + announcements.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -109,14 +75,16 @@ paid_at: { + + //end loop - db.payments.belongsTo(db.orders, { - as: 'order', + db.announcements.belongsTo(db.courses, { + as: 'course', foreignKey: { - name: 'orderId', + name: 'courseId', }, constraints: false, }); @@ -124,18 +92,18 @@ paid_at: { - db.payments.belongsTo(db.users, { + db.announcements.belongsTo(db.users, { as: 'createdBy', }); - db.payments.belongsTo(db.users, { + db.announcements.belongsTo(db.users, { as: 'updatedBy', }); }; - return payments; + return announcements; }; diff --git a/backend/src/db/models/certificates.js b/backend/src/db/models/certificates.js new file mode 100644 index 0000000..daa1c83 --- /dev/null +++ b/backend/src/db/models/certificates.js @@ -0,0 +1,110 @@ +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 certificates = sequelize.define( + 'certificates', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + +serial: { + type: DataTypes.TEXT, + + + + }, + +issued_at: { + type: DataTypes.DATE, + + + + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + certificates.associate = (db) => { + + +/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + + + + + + + + + + + + + + + +//end loop + + + + db.certificates.belongsTo(db.users, { + as: 'student', + foreignKey: { + name: 'studentId', + }, + constraints: false, + }); + + db.certificates.belongsTo(db.courses, { + as: 'course', + foreignKey: { + name: 'courseId', + }, + constraints: false, + }); + + + + db.certificates.hasMany(db.file, { + as: 'file', + foreignKey: 'belongsToId', + constraints: false, + scope: { + belongsTo: db.certificates.getTableName(), + belongsToColumn: 'file', + }, + }); + + + db.certificates.belongsTo(db.users, { + as: 'createdBy', + }); + + db.certificates.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + + + return certificates; +}; + + diff --git a/backend/src/db/models/categories.js b/backend/src/db/models/course_categories.js similarity index 71% rename from backend/src/db/models/categories.js rename to backend/src/db/models/course_categories.js index 52c36a9..3cad8ea 100644 --- a/backend/src/db/models/categories.js +++ b/backend/src/db/models/course_categories.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const categories = sequelize.define( - 'categories', + const course_categories = sequelize.define( + 'course_categories', { id: { type: DataTypes.UUID, @@ -19,6 +19,13 @@ name: { + }, + +slug: { + type: DataTypes.TEXT, + + + }, description: { @@ -41,7 +48,7 @@ description: { }, ); - categories.associate = (db) => { + course_categories.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -50,8 +57,9 @@ description: { - db.categories.hasMany(db.products, { - as: 'products_category', + + db.course_categories.hasMany(db.courses, { + as: 'courses_category', foreignKey: { name: 'categoryId', }, @@ -66,33 +74,26 @@ description: { + //end loop - db.categories.belongsTo(db.categories, { - as: 'parent', - foreignKey: { - name: 'parentId', - }, - constraints: false, - }); - - db.categories.belongsTo(db.users, { + db.course_categories.belongsTo(db.users, { as: 'createdBy', }); - db.categories.belongsTo(db.users, { + db.course_categories.belongsTo(db.users, { as: 'updatedBy', }); }; - return categories; + return course_categories; }; diff --git a/backend/src/db/models/courses.js b/backend/src/db/models/courses.js new file mode 100644 index 0000000..51481d3 --- /dev/null +++ b/backend/src/db/models/courses.js @@ -0,0 +1,206 @@ +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 courses = sequelize.define( + 'courses', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + +title: { + type: DataTypes.TEXT, + + + + }, + +short_description: { + type: DataTypes.TEXT, + + + + }, + +description: { + type: DataTypes.TEXT, + + + + }, + +level: { + type: DataTypes.ENUM, + + + + values: [ + +"Beginner", + + +"Intermediate", + + +"Advanced" + + ], + + }, + +language: { + type: DataTypes.TEXT, + + + + }, + +price: { + type: DataTypes.DECIMAL, + + + + }, + +published: { + type: DataTypes.BOOLEAN, + + allowNull: false, + defaultValue: false, + + + + }, + +published_at: { + type: DataTypes.DATE, + + + + }, + +duration: { + type: DataTypes.INTEGER, + + + + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + courses.associate = (db) => { + + +/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity + + + + + + + + db.courses.hasMany(db.lessons, { + as: 'lessons_course', + foreignKey: { + name: 'courseId', + }, + constraints: false, + }); + + + db.courses.hasMany(db.enrollments, { + as: 'enrollments_course', + foreignKey: { + name: 'courseId', + }, + constraints: false, + }); + + + + + + db.courses.hasMany(db.announcements, { + as: 'announcements_course', + foreignKey: { + name: 'courseId', + }, + constraints: false, + }); + + + db.courses.hasMany(db.certificates, { + as: 'certificates_course', + foreignKey: { + name: 'courseId', + }, + constraints: false, + }); + + + +//end loop + + + + db.courses.belongsTo(db.users, { + as: 'instructor', + foreignKey: { + name: 'instructorId', + }, + constraints: false, + }); + + db.courses.belongsTo(db.course_categories, { + as: 'category', + foreignKey: { + name: 'categoryId', + }, + 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, { + as: 'createdBy', + }); + + db.courses.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + + + return courses; +}; + + diff --git a/backend/src/db/models/products.js b/backend/src/db/models/enrollments.js similarity index 63% rename from backend/src/db/models/products.js rename to backend/src/db/models/enrollments.js index e734846..eba0462 100644 --- a/backend/src/db/models/products.js +++ b/backend/src/db/models/enrollments.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const products = sequelize.define( - 'products', + const enrollments = sequelize.define( + 'enrollments', { id: { type: DataTypes.UUID, @@ -14,36 +14,15 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -sku: { +enrollment_code: { type: DataTypes.TEXT, }, -name: { - type: DataTypes.TEXT, - - - - }, - -description: { - type: DataTypes.TEXT, - - - - }, - -price: { - type: DataTypes.DECIMAL, - - - - }, - -stock: { - type: DataTypes.INTEGER, +enrolled_at: { + type: DataTypes.DATE, @@ -56,18 +35,28 @@ status: { values: [ +"pending", + + "active", -"inactive", +"completed", -"archived" +"dropped" ], }, +progress_percent: { + type: DataTypes.DECIMAL, + + + + }, + importHash: { type: DataTypes.STRING(255), allowNull: true, @@ -81,7 +70,7 @@ status: { }, ); - products.associate = (db) => { + enrollments.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -94,13 +83,7 @@ status: { - db.products.hasMany(db.order_items, { - as: 'order_items_product', - foreignKey: { - name: 'productId', - }, - constraints: false, - }); + @@ -110,39 +93,47 @@ status: { - db.products.belongsTo(db.categories, { - as: 'category', + db.enrollments.belongsTo(db.users, { + as: 'student', foreignKey: { - name: 'categoryId', + name: 'studentId', + }, + constraints: false, + }); + + db.enrollments.belongsTo(db.courses, { + as: 'course', + foreignKey: { + name: 'courseId', }, constraints: false, }); - db.products.hasMany(db.file, { - as: 'images', + db.enrollments.hasMany(db.file, { + as: 'certificate', foreignKey: 'belongsToId', constraints: false, scope: { - belongsTo: db.products.getTableName(), - belongsToColumn: 'images', + belongsTo: db.enrollments.getTableName(), + belongsToColumn: 'certificate', }, }); - db.products.belongsTo(db.users, { + db.enrollments.belongsTo(db.users, { as: 'createdBy', }); - db.products.belongsTo(db.users, { + db.enrollments.belongsTo(db.users, { as: 'updatedBy', }); }; - return products; + return enrollments; }; diff --git a/backend/src/db/models/orders.js b/backend/src/db/models/lessons.js similarity index 56% rename from backend/src/db/models/orders.js rename to backend/src/db/models/lessons.js index e5548d8..a7750f8 100644 --- a/backend/src/db/models/orders.js +++ b/backend/src/db/models/lessons.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const orders = sequelize.define( - 'orders', + const lessons = sequelize.define( + 'lessons', { id: { type: DataTypes.UUID, @@ -14,99 +14,56 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -order_number: { +title: { type: DataTypes.TEXT, }, -status: { - type: DataTypes.ENUM, - - - - values: [ - -"Pending", - - -"Processing", - - -"Shipped", - - -"Delivered", - - -"Cancelled", - - -"Returned", - - -"Refunded" - - ], - - }, - -total: { - type: DataTypes.DECIMAL, - - - - }, - -placed_at: { - type: DataTypes.DATE, - - - - }, - -shipped_at: { - type: DataTypes.DATE, - - - - }, - -delivery_date: { - type: DataTypes.DATE, - - - - }, - -payment_status: { - type: DataTypes.ENUM, - - - - values: [ - -"Pending", - - -"Paid", - - -"Failed", - - -"Refunded" - - ], - - }, - -shipping_address: { +content: { type: DataTypes.TEXT, + }, + +order: { + type: DataTypes.INTEGER, + + + + }, + +duration: { + type: DataTypes.INTEGER, + + + + }, + +start_at: { + type: DataTypes.DATE, + + + + }, + +end_at: { + type: DataTypes.DATE, + + + + }, + +is_published: { + type: DataTypes.BOOLEAN, + + allowNull: false, + defaultValue: false, + + + }, importHash: { @@ -122,7 +79,7 @@ shipping_address: { }, ); - orders.associate = (db) => { + lessons.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -135,31 +92,25 @@ shipping_address: { - db.orders.hasMany(db.order_items, { - as: 'order_items_order', + db.lessons.hasMany(db.progress, { + as: 'progress_lesson', foreignKey: { - name: 'orderId', + name: 'lessonId', }, constraints: false, }); - db.orders.hasMany(db.payments, { - as: 'payments_order', + db.lessons.hasMany(db.quizzes, { + as: 'quizzes_lesson', foreignKey: { - name: 'orderId', + name: 'lessonId', }, constraints: false, }); - db.orders.hasMany(db.shipments, { - as: 'shipments_order', - foreignKey: { - name: 'orderId', - }, - constraints: false, - }); + @@ -167,29 +118,39 @@ shipping_address: { - db.orders.belongsTo(db.customers, { - as: 'customer', + db.lessons.belongsTo(db.courses, { + as: 'course', foreignKey: { - name: 'customerId', + name: 'courseId', }, constraints: false, }); + db.lessons.hasMany(db.file, { + as: 'video_files', + foreignKey: 'belongsToId', + constraints: false, + scope: { + belongsTo: db.lessons.getTableName(), + belongsToColumn: 'video_files', + }, + }); - db.orders.belongsTo(db.users, { + + db.lessons.belongsTo(db.users, { as: 'createdBy', }); - db.orders.belongsTo(db.users, { + db.lessons.belongsTo(db.users, { as: 'updatedBy', }); }; - return orders; + return lessons; }; diff --git a/backend/src/db/models/permissions.js b/backend/src/db/models/permissions.js index 923a603..2abb77f 100644 --- a/backend/src/db/models/permissions.js +++ b/backend/src/db/models/permissions.js @@ -51,6 +51,8 @@ name: { + + //end loop diff --git a/backend/src/db/models/customers.js b/backend/src/db/models/progress.js similarity index 69% rename from backend/src/db/models/customers.js rename to backend/src/db/models/progress.js index c53bffe..6caa560 100644 --- a/backend/src/db/models/customers.js +++ b/backend/src/db/models/progress.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const customers = sequelize.define( - 'customers', + const progress = sequelize.define( + 'progress', { id: { type: DataTypes.UUID, @@ -14,42 +14,14 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -name: { +summary: { type: DataTypes.TEXT, }, -email: { - type: DataTypes.TEXT, - - - - }, - -phone: { - type: DataTypes.TEXT, - - - - }, - -address: { - type: DataTypes.TEXT, - - - - }, - -notes: { - type: DataTypes.TEXT, - - - - }, - -vip: { +completed: { type: DataTypes.BOOLEAN, allowNull: false, @@ -59,7 +31,21 @@ vip: { }, -tax_number: { +completed_at: { + type: DataTypes.DATE, + + + + }, + +percent: { + type: DataTypes.DECIMAL, + + + + }, + +notes: { type: DataTypes.TEXT, @@ -79,7 +65,7 @@ tax_number: { }, ); - customers.associate = (db) => { + progress.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -91,13 +77,7 @@ tax_number: { - db.customers.hasMany(db.orders, { - as: 'orders_customer', - foreignKey: { - name: 'customerId', - }, - constraints: false, - }); + @@ -108,21 +88,37 @@ tax_number: { + db.progress.belongsTo(db.users, { + as: 'student', + foreignKey: { + name: 'studentId', + }, + constraints: false, + }); + + db.progress.belongsTo(db.lessons, { + as: 'lesson', + foreignKey: { + name: 'lessonId', + }, + constraints: false, + }); - db.customers.belongsTo(db.users, { + + db.progress.belongsTo(db.users, { as: 'createdBy', }); - db.customers.belongsTo(db.users, { + db.progress.belongsTo(db.users, { as: 'updatedBy', }); }; - return customers; + return progress; }; diff --git a/backend/src/db/models/shipments.js b/backend/src/db/models/quiz_questions.js similarity index 69% rename from backend/src/db/models/shipments.js rename to backend/src/db/models/quiz_questions.js index 1c5060f..6c0b0b2 100644 --- a/backend/src/db/models/shipments.js +++ b/backend/src/db/models/quiz_questions.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const shipments = sequelize.define( - 'shipments', + const quiz_questions = sequelize.define( + 'quiz_questions', { id: { type: DataTypes.UUID, @@ -14,56 +14,46 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -carrier: { +question: { type: DataTypes.TEXT, }, -tracking_number: { - type: DataTypes.TEXT, - - - - }, - -shipped_at: { - type: DataTypes.DATE, - - - - }, - -delivered_at: { - type: DataTypes.DATE, - - - - }, - -status: { +question_type: { type: DataTypes.ENUM, values: [ -"Pending", +"multiple_choice", -"InTransit", +"true_false", -"Delivered", - - -"Returned" +"short_answer" ], }, +choices: { + type: DataTypes.TEXT, + + + + }, + +answer: { + type: DataTypes.TEXT, + + + + }, + importHash: { type: DataTypes.STRING(255), allowNull: true, @@ -77,7 +67,7 @@ status: { }, ); - shipments.associate = (db) => { + quiz_questions.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -94,14 +84,16 @@ status: { + + //end loop - db.shipments.belongsTo(db.orders, { - as: 'order', + db.quiz_questions.belongsTo(db.quizzes, { + as: 'quiz', foreignKey: { - name: 'orderId', + name: 'quizId', }, constraints: false, }); @@ -109,18 +101,18 @@ status: { - db.shipments.belongsTo(db.users, { + db.quiz_questions.belongsTo(db.users, { as: 'createdBy', }); - db.shipments.belongsTo(db.users, { + db.quiz_questions.belongsTo(db.users, { as: 'updatedBy', }); }; - return shipments; + return quiz_questions; }; diff --git a/backend/src/db/models/order_items.js b/backend/src/db/models/quizzes.js similarity index 63% rename from backend/src/db/models/order_items.js rename to backend/src/db/models/quizzes.js index 8ae8eb3..3ea78af 100644 --- a/backend/src/db/models/order_items.js +++ b/backend/src/db/models/quizzes.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function(sequelize, DataTypes) { - const order_items = sequelize.define( - 'order_items', + const quizzes = sequelize.define( + 'quizzes', { id: { type: DataTypes.UUID, @@ -14,29 +14,39 @@ module.exports = function(sequelize, DataTypes) { primaryKey: true, }, -name: { +title: { type: DataTypes.TEXT, }, -quantity: { +description: { + type: DataTypes.TEXT, + + + + }, + +passing_score: { type: DataTypes.INTEGER, }, -unit_price: { - type: DataTypes.DECIMAL, +time_limit: { + type: DataTypes.INTEGER, }, -total_price: { - type: DataTypes.DECIMAL, +is_active: { + type: DataTypes.BOOLEAN, + + allowNull: false, + defaultValue: false, @@ -55,7 +65,7 @@ total_price: { }, ); - order_items.associate = (db) => { + quizzes.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity @@ -70,24 +80,26 @@ total_price: { + db.quizzes.hasMany(db.quiz_questions, { + as: 'quiz_questions_quiz', + foreignKey: { + name: 'quizId', + }, + constraints: false, + }); + + + //end loop - db.order_items.belongsTo(db.orders, { - as: 'order', + db.quizzes.belongsTo(db.lessons, { + as: 'lesson', foreignKey: { - name: 'orderId', - }, - constraints: false, - }); - - db.order_items.belongsTo(db.products, { - as: 'product', - foreignKey: { - name: 'productId', + name: 'lessonId', }, constraints: false, }); @@ -95,18 +107,18 @@ total_price: { - db.order_items.belongsTo(db.users, { + db.quizzes.belongsTo(db.users, { as: 'createdBy', }); - db.order_items.belongsTo(db.users, { + db.quizzes.belongsTo(db.users, { as: 'updatedBy', }); }; - return order_items; + return quizzes; }; diff --git a/backend/src/db/models/roles.js b/backend/src/db/models/roles.js index 713b308..63ad201 100644 --- a/backend/src/db/models/roles.js +++ b/backend/src/db/models/roles.js @@ -84,6 +84,8 @@ role_customization: { + + //end loop diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js index 0117659..9763813 100644 --- a/backend/src/db/models/users.js +++ b/backend/src/db/models/users.js @@ -145,11 +145,45 @@ provider: { + db.users.hasMany(db.courses, { + as: 'courses_instructor', + foreignKey: { + name: 'instructorId', + }, + constraints: false, + }); + + + + db.users.hasMany(db.enrollments, { + as: 'enrollments_student', + foreignKey: { + name: 'studentId', + }, + constraints: false, + }); + + + db.users.hasMany(db.progress, { + as: 'progress_student', + foreignKey: { + name: 'studentId', + }, + constraints: false, + }); + db.users.hasMany(db.certificates, { + as: 'certificates_student', + foreignKey: { + name: 'studentId', + }, + 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 e32f821..ca02d05 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("StoreOwner"), name: "Store Owner", createdAt, updatedAt }, + { id: getId("PlatformOwner"), name: "Platform Owner", createdAt, updatedAt }, - { id: getId("OperationsManager"), name: "Operations Manager", createdAt, updatedAt }, + { id: getId("LeadInstructor"), name: "Lead Instructor", createdAt, updatedAt }, - { id: getId("FulfillmentLead"), name: "Fulfillment Lead", createdAt, updatedAt }, + { id: getId("CourseManager"), name: "Course Manager", createdAt, updatedAt }, - { id: getId("SalesRepresentative"), name: "Sales Representative", createdAt, updatedAt }, + { id: getId("Instructor"), name: "Instructor", createdAt, updatedAt }, - { id: getId("SupportAgent"), name: "Support Agent", createdAt, updatedAt }, + { id: getId("Student"), name: "Student", createdAt, updatedAt }, @@ -61,7 +61,7 @@ module.exports = { } const entities = [ - "users","roles","permissions","products","categories","customers","orders","order_items","payments","shipments",, + "users","roles","permissions","course_categories","courses","lessons","enrollments","progress","quizzes","quiz_questions","announcements","certificates",, ]; 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("StoreOwner"), permissionId: getId('CREATE_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_USERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_USERS') }, @@ -113,7 +113,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_USERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_USERS') }, @@ -128,6 +128,8 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_USERS') }, + @@ -141,6 +143,8 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_USERS') }, + @@ -154,6 +158,8 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_USERS') }, + @@ -177,40 +183,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_PRODUCTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_COURSE_CATEGORIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_PRODUCTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_COURSE_CATEGORIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_PRODUCTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_COURSE_CATEGORIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_PRODUCTS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_PRODUCTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_PRODUCTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('UPDATE_PRODUCTS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('DELETE_PRODUCTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_COURSE_CATEGORIES') }, @@ -221,7 +206,43 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('READ_PRODUCTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_COURSE_CATEGORIES') }, + + + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_COURSE_CATEGORIES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_COURSE_CATEGORIES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_COURSE_CATEGORIES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('DELETE_COURSE_CATEGORIES') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_COURSE_CATEGORIES') }, @@ -236,22 +257,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('READ_PRODUCTS') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('READ_PRODUCTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_COURSE_CATEGORIES') }, @@ -274,19 +280,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_COURSES') }, @@ -295,19 +301,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('UPDATE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_COURSES') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('DELETE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('DELETE_COURSES') }, @@ -316,12 +322,37 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - - - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('READ_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_COURSES') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_COURSES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_COURSES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), 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') }, + @@ -333,22 +364,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('READ_CATEGORIES') }, - - - - - - - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('READ_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_COURSES') }, @@ -371,19 +387,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_LESSONS') }, @@ -392,19 +408,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('UPDATE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('DELETE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('DELETE_LESSONS') }, @@ -413,14 +429,20 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - - - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('READ_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_LESSONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_LESSONS') }, + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_LESSONS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('DELETE_LESSONS') }, + @@ -428,15 +450,15 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('CREATE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('READ_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_LESSONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('UPDATE_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_LESSONS') }, @@ -449,7 +471,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('READ_CUSTOMERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_LESSONS') }, @@ -472,40 +494,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_ORDERS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_ORDERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_ORDERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('UPDATE_ORDERS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('DELETE_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_ENROLLMENTS') }, @@ -516,11 +517,11 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('READ_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('UPDATE_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_ENROLLMENTS') }, @@ -531,11 +532,30 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('CREATE_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_ENROLLMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('READ_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_ENROLLMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_ENROLLMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('DELETE_ENROLLMENTS') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_ENROLLMENTS') }, @@ -548,9 +568,11 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('CREATE_ENROLLMENTS') }, + - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('READ_ORDERS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_ENROLLMENTS') }, @@ -573,40 +595,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_ORDER_ITEMS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_ORDER_ITEMS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_ORDER_ITEMS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_ORDER_ITEMS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_ORDER_ITEMS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_ORDER_ITEMS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('UPDATE_ORDER_ITEMS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('DELETE_ORDER_ITEMS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_PROGRESS') }, @@ -617,30 +618,11 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('READ_ORDER_ITEMS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_PROGRESS') }, - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('UPDATE_ORDER_ITEMS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('DELETE_ORDER_ITEMS') }, - - - - - - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('CREATE_ORDER_ITEMS') }, - - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('READ_ORDER_ITEMS') }, - - + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_PROGRESS') }, @@ -653,7 +635,43 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('READ_ORDER_ITEMS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_PROGRESS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_PROGRESS') }, + + + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_PROGRESS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_PROGRESS') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('CREATE_PROGRESS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_PROGRESS') }, @@ -676,19 +694,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_QUIZZES') }, @@ -697,19 +715,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('UPDATE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('DELETE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('DELETE_QUIZZES') }, @@ -718,14 +736,20 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - - - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('READ_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_QUIZZES') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_QUIZZES') }, + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_QUIZZES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('DELETE_QUIZZES') }, + @@ -733,14 +757,16 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('CREATE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('READ_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_QUIZZES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_QUIZZES') }, + @@ -752,7 +778,7 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('READ_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_QUIZZES') }, @@ -775,19 +801,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('READ_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('UPDATE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('DELETE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_QUIZ_QUESTIONS') }, @@ -796,19 +822,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('READ_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('UPDATE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('DELETE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('DELETE_QUIZ_QUESTIONS') }, @@ -817,19 +843,19 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('CREATE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('READ_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('UPDATE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_QUIZ_QUESTIONS') }, - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('DELETE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('DELETE_QUIZ_QUESTIONS') }, @@ -838,12 +864,16 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - - - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('READ_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_QUIZ_QUESTIONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_QUIZ_QUESTIONS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_QUIZ_QUESTIONS') }, + @@ -855,7 +885,223 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('READ_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_QUIZ_QUESTIONS') }, + + + + + + + + + + + + + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_ANNOUNCEMENTS') }, + + + + + + + + + + + + + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('READ_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('UPDATE_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('DELETE_CERTIFICATES') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('READ_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('UPDATE_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('DELETE_CERTIFICATES') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('READ_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('UPDATE_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('DELETE_CERTIFICATES') }, + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('READ_CERTIFICATES') }, + + + + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('UPDATE_CERTIFICATES') }, + + + + + + + + + + + + + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('READ_CERTIFICATES') }, @@ -873,15 +1119,15 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - { createdAt, updatedAt, roles_permissionsId: getId("StoreOwner"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("PlatformOwner"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("OperationsManager"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("LeadInstructor"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("FulfillmentLead"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("CourseManager"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("SalesRepresentative"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("Instructor"), permissionId: getId('CREATE_SEARCH') }, - { createdAt, updatedAt, roles_permissionsId: getId("SupportAgent"), permissionId: getId('CREATE_SEARCH') }, + { createdAt, updatedAt, roles_permissionsId: getId("Student"), permissionId: getId('CREATE_SEARCH') }, @@ -901,40 +1147,50 @@ 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_PRODUCTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PRODUCTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PRODUCTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PRODUCTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_COURSE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_COURSE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_COURSE_CATEGORIES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_COURSE_CATEGORIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_CATEGORIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_CATEGORIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_CATEGORIES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_CATEGORIES') }, + { 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_CUSTOMERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_CUSTOMERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_CUSTOMERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_CUSTOMERS') }, + { 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_ORDERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ORDERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ORDERS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ORDERS') }, + { 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_ORDER_ITEMS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ORDER_ITEMS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ORDER_ITEMS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ORDER_ITEMS') }, + { 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_PAYMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_PAYMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_PAYMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_PAYMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_QUIZZES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_QUIZZES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_QUIZZES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_QUIZZES') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_SHIPMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_SHIPMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_SHIPMENTS') }, - { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_SHIPMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_QUIZ_QUESTIONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_QUIZ_QUESTIONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_QUIZ_QUESTIONS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_QUIZ_QUESTIONS') }, + + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_ANNOUNCEMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_ANNOUNCEMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_ANNOUNCEMENTS') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_ANNOUNCEMENTS') }, + + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('CREATE_CERTIFICATES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('READ_CERTIFICATES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('UPDATE_CERTIFICATES') }, + { createdAt, updatedAt, roles_permissionsId: getId("Administrator"), permissionId: getId('DELETE_CERTIFICATES') }, @@ -951,8 +1207,8 @@ await queryInterface.bulkInsert("rolesPermissionsPermissions", [ - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("StoreOwner")}' WHERE "email"='client@hello.com'`); - await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("OperationsManager")}' WHERE "email"='john@doe.com'`); + await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("PlatformOwner")}' WHERE "email"='client@hello.com'`); + await queryInterface.sequelize.query(`UPDATE "users" SET "app_roleId"='${getId("LeadInstructor")}' 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 70d6149..1503cc6 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -28,19 +28,23 @@ const Users = db.users; -const Products = db.products; +const CourseCategories = db.course_categories; -const Categories = db.categories; +const Courses = db.courses; -const Customers = db.customers; +const Lessons = db.lessons; -const Orders = db.orders; +const Enrollments = db.enrollments; -const OrderItems = db.order_items; +const Progress = db.progress; -const Payments = db.payments; +const Quizzes = db.quizzes; -const Shipments = db.shipments; +const QuizQuestions = db.quiz_questions; + +const Announcements = db.announcements; + +const Certificates = db.certificates; @@ -48,28 +52,162 @@ const Shipments = db.shipments; -const ProductsData = [ +const CourseCategoriesData = [ { - "sku": "ELEC-1001", + "name": "Web Development", - "name": "Wireless Headphones", + "slug": "web-development", - "description": "Over ear wireless headphones with noise cancellation", + "description": "Practical courses on building modern web applications", + + + + }, + + { + + + + + "name": "Data Science", + + + + + + + "slug": "data-science", + + + + + + + "description": "Analytics and machine learning fundamentals and projects", + + + + }, + + { + + + + + "name": "Design", + + + + + + + "slug": "design", + + + + + + + "description": "UI UX and product design principles and tools", + + + + }, + + { + + + + + "name": "Business", + + + + + + + "slug": "business", + + + + + + + "description": "Entrepreneurship and product management essentials", + + + + }, + +]; + + + +const CoursesData = [ + + { + + + + + "title": "Modern React from Scratch", + + + + + + + "short_description": "Learn React fundamentals and modern patterns", + + + + + + + "description": "A hands on course covering React hooks routing state management and testing", + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "level": "Advanced", + + + + + + + "language": "English", @@ -83,7 +221,14 @@ const ProductsData = [ - "stock": 120, + "published": true, + + + + + + + "published_at": new Date('2025-11-10T10:00:00Z'), @@ -97,14 +242,7 @@ const ProductsData = [ - // type code here for "relation_one" field - - - - - - - "status": "inactive", + "duration": 480, @@ -115,35 +253,70 @@ const ProductsData = [ - "sku": "APP-2002", + "title": "Data Science Practical Projects", - "name": "Classic T-Shirt", + "short_description": "Project oriented data science with Python", - "description": "100 percent cotton classic fit t shirt", + "description": "Build end to end analytics projects including cleaning modeling and deployment", - "price": 19.5, + // type code here for "relation_one" field - "stock": 540, + // type code here for "relation_one" field + + + + + + + "level": "Advanced", + + + + + + + "language": "English", + + + + + + + "price": 199.0, + + + + + + + "published": true, + + + + + + + "published_at": new Date('2025-09-05T09:00:00Z'), @@ -157,14 +330,7 @@ const ProductsData = [ - // type code here for "relation_one" field - - - - - - - "status": "inactive", + "duration": 720, @@ -175,35 +341,70 @@ const ProductsData = [ - "sku": "HOME-3003", + "title": "Product Design Essentials", - "name": "Ceramic Vase", + "short_description": "Core principles of UI and UX design", - "description": "Decorative ceramic vase in matte finish", + "description": "From research to high fidelity prototypes this course covers practical design workflows", - "price": 45.0, + // type code here for "relation_one" field - "stock": 75, + // type code here for "relation_one" field + + + + + + + "level": "Intermediate", + + + + + + + "language": "English", + + + + + + + "price": 79.0, + + + + + + + "published": true, + + + + + + + "published_at": new Date('2025-10-20T08:30:00Z'), @@ -217,14 +418,7 @@ const ProductsData = [ - // type code here for "relation_one" field - - - - - - - "status": "active", + "duration": 360, @@ -235,35 +429,70 @@ const ProductsData = [ - "sku": "ACC-4004", + "title": "Startup Product Management", - "name": "Leather Wallet", + "short_description": "Lean product management for startups", - "description": "Compact leather wallet with multiple card slots", + "description": "Roadmapping prioritization and metrics to grow products in early stage companies", - "price": 39.99, + // type code here for "relation_one" field - "stock": 210, + // type code here for "relation_one" field + + + + + + + "level": "Advanced", + + + + + + + "language": "English", + + + + + + + "price": 149.0, + + + + + + + "published": false, + + + + + + + "published_at": new Date('2026-03-01T09:00:00Z'), @@ -277,74 +506,7 @@ const ProductsData = [ - // type code here for "relation_one" field - - - - - - - "status": "inactive", - - - - }, - - { - - - - - "sku": "CLR-5005", - - - - - - - "name": "Clearance Mug", - - - - - - - "description": "Ceramic mug limited stock clearance", - - - - - - - "price": 6.99, - - - - - - - "stock": 30, - - - - - - - // type code here for "images" field - - - - - - - // type code here for "relation_one" field - - - - - - - "status": "active", + "duration": 420, @@ -354,21 +516,14 @@ const ProductsData = [ -const CategoriesData = [ +const LessonsData = [ { - "name": "Electronics", - - - - - - - "description": "Devices and gadgets for everyday use", + "title": "React Components and Props", @@ -379,6 +534,55 @@ const CategoriesData = [ + + + + "content": "Introduction to components props and composition with examples", + + + + + + + // type code here for "files" field + + + + + + + "order": 1, + + + + + + + "duration": 25, + + + + + + + "start_at": new Date('2026-02-01T09:00:00Z'), + + + + + + + "end_at": new Date('2026-02-01T09:30:00Z'), + + + + + + + "is_published": true, + + + }, { @@ -386,14 +590,7 @@ const CategoriesData = [ - "name": "Apparel", - - - - - - - "description": "Clothing and wearable accessories", + "title": "State and Lifecycle", @@ -404,6 +601,55 @@ const CategoriesData = [ + + + + "content": "State management patterns using hooks and lifecycles", + + + + + + + // type code here for "files" field + + + + + + + "order": 2, + + + + + + + "duration": 35, + + + + + + + "start_at": new Date('2026-02-02T09:00:00Z'), + + + + + + + "end_at": new Date('2026-02-02T09:40:00Z'), + + + + + + + "is_published": true, + + + }, { @@ -411,14 +657,7 @@ const CategoriesData = [ - "name": "Home Goods", - - - - - - - "description": "Items for home and living spaces", + "title": "Exploratory Data Analysis", @@ -429,6 +668,55 @@ const CategoriesData = [ + + + + "content": "Techniques for data cleaning visualization and initial modeling", + + + + + + + // type code here for "files" field + + + + + + + "order": 1, + + + + + + + "duration": 45, + + + + + + + "start_at": new Date('2026-02-05T10:00:00Z'), + + + + + + + "end_at": new Date('2026-02-05T10:45:00Z'), + + + + + + + "is_published": true, + + + }, { @@ -436,14 +724,7 @@ const CategoriesData = [ - "name": "Accessories", - - - - - - - "description": "Complementary items and extras", + "title": "Design Research Methods", @@ -454,28 +735,52 @@ const CategoriesData = [ - }, - - { - - "name": "Clearance", + "content": "User interviews journey mapping and synthesis techniques", - "description": "Discounted and clearance items", + // type code here for "files" field - // type code here for "relation_one" field + "order": 1, + + + + + + + "duration": 40, + + + + + + + "start_at": new Date('2026-02-07T11:00:00Z'), + + + + + + + "end_at": new Date('2026-02-07T11:40:00Z'), + + + + + + + "is_published": true, @@ -485,56 +790,56 @@ const CategoriesData = [ -const CustomersData = [ +const EnrollmentsData = [ { - "name": "Global Enterprises LLC", + "enrollment_code": "ENR-1001", - "email": "contact@globalenterprises.com", + // type code here for "relation_one" field - "phone": "+1-415-555-0100", + // type code here for "relation_one" field - "address": "120 Market St, San Francisco CA 94103", + "enrolled_at": new Date('2026-01-05T12:00:00Z'), - "notes": "B2B account with net30 terms", + "status": "dropped", - "vip": true, + "progress_percent": 45.0, - "tax_number": "TAX-998877", + // type code here for "files" field @@ -545,49 +850,49 @@ const CustomersData = [ - "name": "Hannah Rogers", + "enrollment_code": "ENR-1002", - "email": "hannah.rogers@example.com", + // type code here for "relation_one" field - "phone": "+1-212-555-0175", + // type code here for "relation_one" field - "address": "45 West 21st St, New York NY 10010", + "enrolled_at": new Date('2026-01-07T15:20:00Z'), - "notes": "Frequent buyer of apparel", + "status": "completed", - "vip": false, + "progress_percent": 20.0, - "tax_number": "", + // type code here for "files" field @@ -598,49 +903,49 @@ const CustomersData = [ - "name": "Ibrahim Noor", + "enrollment_code": "ENR-1003", - "email": "ibrahim.noor@example.com", + // type code here for "relation_one" field - "phone": "+44-20-7946-0958", + // type code here for "relation_one" field - "address": "8 Baker St, London NW1 6XE", + "enrolled_at": new Date('2026-01-02T09:10:00Z'), - "notes": "Preferred shipping to office address", + "status": "completed", - "vip": true, + "progress_percent": 100.0, - "tax_number": "GB-TAX-5544", + // type code here for "files" field @@ -651,102 +956,49 @@ const CustomersData = [ - "name": "Jessica Patel", + "enrollment_code": "ENR-1004", - "email": "jessica.patel@example.com", + // type code here for "relation_one" field - "phone": "+61-2-9374-4000", + // type code here for "relation_one" field - "address": "200 George St, Sydney NSW 2000", + "enrolled_at": new Date('2026-01-09T10:45:00Z'), - "notes": "Gift orders frequently", + "status": "completed", - "vip": false, + "progress_percent": 0.0, - "tax_number": "", - - - - }, - - { - - - - - "name": "Kevin OConnor", - - - - - - - "email": "kevin.oconnor@example.com", - - - - - - - "phone": "+353-1-555-0199", - - - - - - - "address": "10 Trinity St, Dublin D02", - - - - - - - "notes": "Requires VAT invoices", - - - - - - - "vip": true, - - - - - - - "tax_number": "IE-VAT-2233", + // type code here for "files" field @@ -756,14 +1008,14 @@ const CustomersData = [ -const OrdersData = [ +const ProgressData = [ { - "order_number": "ORD-1001", + "summary": "Completed React components lesson", @@ -777,49 +1029,35 @@ const OrdersData = [ - "status": "Cancelled", + // type code here for "relation_one" field - "total": 259.98, + "completed": true, - "placed_at": new Date('2025-11-10T09:15:00Z'), + "completed_at": new Date('2026-01-06T14:00:00Z'), - "shipped_at": new Date('2025-11-11T14:30:00Z'), + "percent": 100.0, - "delivery_date": new Date('2025-11-14T16:00:00Z'), - - - - - - - "payment_status": "Paid", - - - - - - - "shipping_address": "120 Market St, San Francisco CA 94103", + "notes": "Reviewed examples and completed exercises", @@ -830,7 +1068,7 @@ const OrdersData = [ - "order_number": "ORD-1002", + "summary": "Started state and lifecycle lesson", @@ -844,49 +1082,35 @@ const OrdersData = [ - "status": "Returned", + // type code here for "relation_one" field - "total": 19.5, + "completed": true, - "placed_at": new Date('2025-11-12T11:20:00Z'), + "completed_at": new Date(Date.now()), - "shipped_at": new Date(Date.now()), + "percent": 40.0, - "delivery_date": new Date(Date.now()), - - - - - - - "payment_status": "Failed", - - - - - - - "shipping_address": "45 West 21st St, New York NY 10010", + "notes": "Working through hooks section", @@ -897,7 +1121,7 @@ const OrdersData = [ - "order_number": "ORD-1003", + "summary": "Finished EDA assignment", @@ -911,49 +1135,35 @@ const OrdersData = [ - "status": "Pending", + // type code here for "relation_one" field - "total": 45.0, + "completed": true, - "placed_at": new Date('2025-11-09T08:05:00Z'), + "completed_at": new Date('2026-01-12T16:30:00Z'), - "shipped_at": new Date('2025-11-10T10:00:00Z'), + "percent": 100.0, - "delivery_date": new Date('2025-11-13T12:00:00Z'), - - - - - - - "payment_status": "Failed", - - - - - - - "shipping_address": "8 Baker St, London NW1 6XE", + "notes": "Submitted notebook and visualizations", @@ -964,7 +1174,7 @@ const OrdersData = [ - "order_number": "ORD-1004", + "summary": "Watched design research lecture", @@ -978,66 +1188,6 @@ const OrdersData = [ - "status": "Cancelled", - - - - - - - "total": 39.99, - - - - - - - "placed_at": new Date('2025-11-08T15:40:00Z'), - - - - - - - "shipped_at": new Date(Date.now()), - - - - - - - "delivery_date": new Date(Date.now()), - - - - - - - "payment_status": "Paid", - - - - - - - "shipping_address": "200 George St, Sydney NSW 2000", - - - - }, - - { - - - - - "order_number": "ORD-1005", - - - - - - // type code here for "relation_one" field @@ -1045,49 +1195,28 @@ const OrdersData = [ - "status": "Cancelled", + "completed": true, - "total": 6.99, + "completed_at": new Date('2026-01-08T11:50:00Z'), - "placed_at": new Date('2025-11-13T10:50:00Z'), + "percent": 100.0, - "shipped_at": new Date('2025-11-14T09:00:00Z'), - - - - - - - "delivery_date": new Date('2025-11-17T15:00:00Z'), - - - - - - - "payment_status": "Failed", - - - - - - - "shipping_address": "10 Trinity St, Dublin D02", + "notes": "Took notes on interview techniques", @@ -1097,14 +1226,14 @@ const OrdersData = [ -const OrderItemsData = [ +const QuizzesData = [ { - "name": "Wireless Headphones", + "title": "React Basics Quiz", @@ -1118,28 +1247,28 @@ const OrderItemsData = [ - // type code here for "relation_one" field + "description": "Short quiz on components and props", - "quantity": 2, + "passing_score": 70, - "unit_price": 129.99, + "time_limit": 15, - "total_price": 259.98, + "is_active": true, @@ -1150,7 +1279,7 @@ const OrderItemsData = [ - "name": "Classic T-Shirt", + "title": "React State Quiz", @@ -1164,28 +1293,28 @@ const OrderItemsData = [ - // type code here for "relation_one" field + "description": "Assess understanding of state and hooks", - "quantity": 1, + "passing_score": 75, - "unit_price": 19.5, + "time_limit": 20, - "total_price": 19.5, + "is_active": true, @@ -1196,7 +1325,7 @@ const OrderItemsData = [ - "name": "Ceramic Vase", + "title": "EDA Checkpoint", @@ -1210,28 +1339,28 @@ const OrderItemsData = [ - // type code here for "relation_one" field + "description": "Questions about exploratory data analysis techniques", - "quantity": 1, + "passing_score": 70, - "unit_price": 45.0, + "time_limit": 30, - "total_price": 45.0, + "is_active": true, @@ -1242,7 +1371,7 @@ const OrderItemsData = [ - "name": "Leather Wallet", + "title": "Design Research Quiz", @@ -1256,74 +1385,28 @@ const OrderItemsData = [ - // type code here for "relation_one" field + "description": "Evaluate knowledge of user research methods", - "quantity": 1, + "passing_score": 65, - "unit_price": 39.99, + "time_limit": 20, - "total_price": 39.99, - - - - }, - - { - - - - - "name": "Clearance Mug", - - - - - - - // type code here for "relation_one" field - - - - - - - // type code here for "relation_one" field - - - - - - - "quantity": 1, - - - - - - - "unit_price": 6.99, - - - - - - - "total_price": 6.99, + "is_active": true, @@ -1333,20 +1416,13 @@ const OrderItemsData = [ -const PaymentsData = [ +const QuizQuestionsData = [ { - "reference": "PAY-9001", - - - - - - // type code here for "relation_one" field @@ -1354,28 +1430,28 @@ const PaymentsData = [ - "amount": 259.98, + "question": "What is a React functional component", - "method": "Cash", + "question_type": "multiple_choice", - "status": "Failed", + "choices": "", - "paid_at": new Date('2025-11-10T09:30:00Z'), + "answer": "Grace Hopper", @@ -1386,13 +1462,6 @@ const PaymentsData = [ - "reference": "PAY-9002", - - - - - - // type code here for "relation_one" field @@ -1400,28 +1469,28 @@ const PaymentsData = [ - "amount": 19.5, + "question": "Which prop pattern passes data from parent to child", - "method": "PayPal", + "question_type": "short_answer", - "status": "Refunded", + "choices": "props|state|context|lifecycle", - "paid_at": new Date(Date.now()), + "answer": "Alan Turing", @@ -1432,13 +1501,6 @@ const PaymentsData = [ - "reference": "PAY-9003", - - - - - - // type code here for "relation_one" field @@ -1446,28 +1508,28 @@ const PaymentsData = [ - "amount": 45.0, + "question": "Which hook is used for side effects", - "method": "Cash", + "question_type": "short_answer", - "status": "Failed", + "choices": "useEffect|useState|useMemo|useRef", - "paid_at": new Date('2025-11-09T08:20:00Z'), + "answer": "Ada Lovelace", @@ -1478,13 +1540,6 @@ const PaymentsData = [ - "reference": "PAY-9004", - - - - - - // type code here for "relation_one" field @@ -1492,74 +1547,28 @@ const PaymentsData = [ - "amount": 39.99, + "question": "What is a boxplot useful for", - "method": "Cash", + "question_type": "true_false", - "status": "Pending", + "choices": "distribution|modeling|deployment|authentication", - "paid_at": new Date('2025-11-08T16:00:00Z'), - - - - }, - - { - - - - - "reference": "PAY-9005", - - - - - - - // type code here for "relation_one" field - - - - - - - "amount": 6.99, - - - - - - - "method": "BankTransfer", - - - - - - - "status": "Pending", - - - - - - - "paid_at": new Date('2025-11-13T11:10:00Z'), + "answer": "Grace Hopper", @@ -1569,7 +1578,7 @@ const PaymentsData = [ -const ShipmentsData = [ +const AnnouncementsData = [ { @@ -1583,35 +1592,28 @@ const ShipmentsData = [ - "carrier": "FastShip", + "title": "Welcome to Modern React", - "tracking_number": "FS123456789", + "content": "Course kickoff and recommended setup instructions", - "shipped_at": new Date('2025-11-11T14:30:00Z'), + "published_at": new Date('2026-01-05T09:00:00Z'), - "delivered_at": new Date('2025-11-14T16:00:00Z'), - - - - - - - "status": "Pending", + "is_active": true, @@ -1629,35 +1631,28 @@ const ShipmentsData = [ - "carrier": "PostalExpress", + "title": "Project Guidelines", - "tracking_number": "PX987654321", + "content": "Details about the capstone project and deliverables", - "shipped_at": new Date(Date.now()), + "published_at": new Date('2026-01-08T10:00:00Z'), - "delivered_at": new Date(Date.now()), - - - - - - - "status": "Pending", + "is_active": true, @@ -1675,35 +1670,28 @@ const ShipmentsData = [ - "carrier": "GlobalCourier", + "title": "Design Critique Session", - "tracking_number": "GC555222333", + "content": "Sign up for the weekly critique workshop", - "shipped_at": new Date('2025-11-10T10:00:00Z'), + "published_at": new Date('2026-01-07T12:00:00Z'), - "delivered_at": new Date('2025-11-13T12:00:00Z'), - - - - - - - "status": "Returned", + "is_active": true, @@ -1721,45 +1709,51 @@ const ShipmentsData = [ - "carrier": "FastShip", + "title": "Docker Lab Schedule", - "tracking_number": "FS444777888", + "content": "Live lab sessions available next week", - "shipped_at": new Date(Date.now()), + "published_at": new Date('2026-01-09T14:00:00Z'), - "delivered_at": new Date(Date.now()), - - - - - - - "status": "Pending", + "is_active": true, }, +]; + + + +const CertificatesData = [ + { + "serial": "CER-2026-0001", + + + + + + // type code here for "relation_one" field @@ -1767,35 +1761,138 @@ const ShipmentsData = [ - "carrier": "PostalExpress", + // type code here for "relation_one" field - "tracking_number": "PX111333555", + "issued_at": new Date('2026-01-13T10:00:00Z'), - "shipped_at": new Date('2025-11-14T09:00:00Z'), + // type code here for "files" field + + + + }, + + { + + + + + "serial": "CER-2026-0002", - "delivered_at": new Date('2025-11-17T15:00:00Z'), + // type code here for "relation_one" field - "status": "InTransit", + // type code here for "relation_one" field + + + + + + + "issued_at": new Date('2026-01-14T09:00:00Z'), + + + + + + + // type code here for "files" field + + + + }, + + { + + + + + "serial": "CER-2026-0003", + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "issued_at": new Date('2026-01-12T16:45:00Z'), + + + + + + + // type code here for "files" field + + + + }, + + { + + + + + "serial": "CER-2026-0004", + + + + + + + // type code here for "relation_one" field + + + + + + + // type code here for "relation_one" field + + + + + + + "issued_at": new Date('2026-01-15T11:30:00Z'), + + + + + + + // type code here for "files" field @@ -1853,6 +1950,10 @@ const ShipmentsData = [ + + + + @@ -1861,82 +1962,143 @@ const ShipmentsData = [ - async function associateProductWithCategory() { + async function associateCoursWithInstructor() { - const relatedCategory0 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedInstructor0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Product0 = await Products.findOne({ + const Cours0 = await Courses.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Product0?.setCategory) + if (Cours0?.setInstructor) { await - Product0. + Cours0. + setInstructor(relatedInstructor0); + } + + const relatedInstructor1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Cours1 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Cours1?.setInstructor) + { + await + Cours1. + setInstructor(relatedInstructor1); + } + + const relatedInstructor2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Cours2 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Cours2?.setInstructor) + { + await + Cours2. + setInstructor(relatedInstructor2); + } + + const relatedInstructor3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Cours3 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Cours3?.setInstructor) + { + await + Cours3. + setInstructor(relatedInstructor3); + } + + } + + + + + async function associateCoursWithCategory() { + + const relatedCategory0 = await CourseCategories.findOne({ + offset: Math.floor(Math.random() * (await CourseCategories.count())), + }); + const Cours0 = await Courses.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Cours0?.setCategory) + { + await + Cours0. setCategory(relatedCategory0); } - const relatedCategory1 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedCategory1 = await CourseCategories.findOne({ + offset: Math.floor(Math.random() * (await CourseCategories.count())), }); - const Product1 = await Products.findOne({ + const Cours1 = await Courses.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Product1?.setCategory) + if (Cours1?.setCategory) { await - Product1. + Cours1. setCategory(relatedCategory1); } - const relatedCategory2 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedCategory2 = await CourseCategories.findOne({ + offset: Math.floor(Math.random() * (await CourseCategories.count())), }); - const Product2 = await Products.findOne({ + const Cours2 = await Courses.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Product2?.setCategory) + if (Cours2?.setCategory) { await - Product2. + Cours2. setCategory(relatedCategory2); } - const relatedCategory3 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedCategory3 = await CourseCategories.findOne({ + offset: Math.floor(Math.random() * (await CourseCategories.count())), }); - const Product3 = await Products.findOne({ + const Cours3 = await Courses.findOne({ order: [['id', 'ASC']], offset: 3 }); - if (Product3?.setCategory) + if (Cours3?.setCategory) { await - Product3. + Cours3. setCategory(relatedCategory3); } - const relatedCategory4 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), - }); - const Product4 = await Products.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Product4?.setCategory) - { - await - Product4. - setCategory(relatedCategory4); - } - } + + + + + + + + + + + + @@ -1945,180 +2107,63 @@ const ShipmentsData = [ - - - async function associateCategoryWithParent() { + async function associateLessonWithCourse() { - const relatedParent0 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const Category0 = await Categories.findOne({ + const Lesson0 = await Lessons.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Category0?.setParent) + if (Lesson0?.setCourse) { await - Category0. - setParent(relatedParent0); + Lesson0. + setCourse(relatedCourse0); } - const relatedParent1 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const Category1 = await Categories.findOne({ + const Lesson1 = await Lessons.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Category1?.setParent) + if (Lesson1?.setCourse) { await - Category1. - setParent(relatedParent1); + Lesson1. + setCourse(relatedCourse1); } - const relatedParent2 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const Category2 = await Categories.findOne({ + const Lesson2 = await Lessons.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Category2?.setParent) + if (Lesson2?.setCourse) { await - Category2. - setParent(relatedParent2); + Lesson2. + setCourse(relatedCourse2); } - const relatedParent3 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), + const relatedCourse3 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const Category3 = await Categories.findOne({ + const Lesson3 = await Lessons.findOne({ order: [['id', 'ASC']], offset: 3 }); - if (Category3?.setParent) + if (Lesson3?.setCourse) { await - Category3. - setParent(relatedParent3); - } - - const relatedParent4 = await Categories.findOne({ - offset: Math.floor(Math.random() * (await Categories.count())), - }); - const Category4 = await Categories.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Category4?.setParent) - { - await - Category4. - setParent(relatedParent4); - } - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - async function associateOrderWithCustomer() { - - const relatedCustomer0 = await Customers.findOne({ - offset: Math.floor(Math.random() * (await Customers.count())), - }); - const Order0 = await Orders.findOne({ - order: [['id', 'ASC']], - offset: 0 - }); - if (Order0?.setCustomer) - { - await - Order0. - setCustomer(relatedCustomer0); - } - - const relatedCustomer1 = await Customers.findOne({ - offset: Math.floor(Math.random() * (await Customers.count())), - }); - const Order1 = await Orders.findOne({ - order: [['id', 'ASC']], - offset: 1 - }); - if (Order1?.setCustomer) - { - await - Order1. - setCustomer(relatedCustomer1); - } - - const relatedCustomer2 = await Customers.findOne({ - offset: Math.floor(Math.random() * (await Customers.count())), - }); - const Order2 = await Orders.findOne({ - order: [['id', 'ASC']], - offset: 2 - }); - if (Order2?.setCustomer) - { - await - Order2. - setCustomer(relatedCustomer2); - } - - const relatedCustomer3 = await Customers.findOne({ - offset: Math.floor(Math.random() * (await Customers.count())), - }); - const Order3 = await Orders.findOne({ - order: [['id', 'ASC']], - offset: 3 - }); - if (Order3?.setCustomer) - { - await - Order3. - setCustomer(relatedCustomer3); - } - - const relatedCustomer4 = await Customers.findOne({ - offset: Math.floor(Math.random() * (await Customers.count())), - }); - const Order4 = await Orders.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Order4?.setCustomer) - { - await - Order4. - setCustomer(relatedCustomer4); + Lesson3. + setCourse(relatedCourse3); } } @@ -2146,76 +2191,62 @@ const ShipmentsData = [ - async function associateOrderItemWithOrder() { + async function associateEnrollmentWithStudent() { - const relatedOrder0 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const OrderItem0 = await OrderItems.findOne({ + const Enrollment0 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (OrderItem0?.setOrder) + if (Enrollment0?.setStudent) { await - OrderItem0. - setOrder(relatedOrder0); + Enrollment0. + setStudent(relatedStudent0); } - const relatedOrder1 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const OrderItem1 = await OrderItems.findOne({ + const Enrollment1 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (OrderItem1?.setOrder) + if (Enrollment1?.setStudent) { await - OrderItem1. - setOrder(relatedOrder1); + Enrollment1. + setStudent(relatedStudent1); } - const relatedOrder2 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const OrderItem2 = await OrderItems.findOne({ + const Enrollment2 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (OrderItem2?.setOrder) + if (Enrollment2?.setStudent) { await - OrderItem2. - setOrder(relatedOrder2); + Enrollment2. + setStudent(relatedStudent2); } - const relatedOrder3 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const OrderItem3 = await OrderItems.findOne({ + const Enrollment3 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 3 }); - if (OrderItem3?.setOrder) + if (Enrollment3?.setStudent) { await - OrderItem3. - setOrder(relatedOrder3); - } - - const relatedOrder4 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), - }); - const OrderItem4 = await OrderItems.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (OrderItem4?.setOrder) - { - await - OrderItem4. - setOrder(relatedOrder4); + Enrollment3. + setStudent(relatedStudent3); } } @@ -2223,76 +2254,62 @@ const ShipmentsData = [ - async function associateOrderItemWithProduct() { + async function associateEnrollmentWithCourse() { - const relatedProduct0 = await Products.findOne({ - offset: Math.floor(Math.random() * (await Products.count())), + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const OrderItem0 = await OrderItems.findOne({ + const Enrollment0 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (OrderItem0?.setProduct) + if (Enrollment0?.setCourse) { await - OrderItem0. - setProduct(relatedProduct0); + Enrollment0. + setCourse(relatedCourse0); } - const relatedProduct1 = await Products.findOne({ - offset: Math.floor(Math.random() * (await Products.count())), + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const OrderItem1 = await OrderItems.findOne({ + const Enrollment1 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (OrderItem1?.setProduct) + if (Enrollment1?.setCourse) { await - OrderItem1. - setProduct(relatedProduct1); + Enrollment1. + setCourse(relatedCourse1); } - const relatedProduct2 = await Products.findOne({ - offset: Math.floor(Math.random() * (await Products.count())), + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const OrderItem2 = await OrderItems.findOne({ + const Enrollment2 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (OrderItem2?.setProduct) + if (Enrollment2?.setCourse) { await - OrderItem2. - setProduct(relatedProduct2); + Enrollment2. + setCourse(relatedCourse2); } - const relatedProduct3 = await Products.findOne({ - offset: Math.floor(Math.random() * (await Products.count())), + const relatedCourse3 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), }); - const OrderItem3 = await OrderItems.findOne({ + const Enrollment3 = await Enrollments.findOne({ order: [['id', 'ASC']], offset: 3 }); - if (OrderItem3?.setProduct) + if (Enrollment3?.setCourse) { await - OrderItem3. - setProduct(relatedProduct3); - } - - const relatedProduct4 = await Products.findOne({ - offset: Math.floor(Math.random() * (await Products.count())), - }); - const OrderItem4 = await OrderItems.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (OrderItem4?.setProduct) - { - await - OrderItem4. - setProduct(relatedProduct4); + Enrollment3. + setCourse(relatedCourse3); } } @@ -2304,6 +2321,8 @@ const ShipmentsData = [ + + @@ -2312,76 +2331,202 @@ const ShipmentsData = [ - async function associatePaymentWithOrder() { + async function associateProgresWithStudent() { - const relatedOrder0 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Payment0 = await Payments.findOne({ + const Progres0 = await Progress.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Payment0?.setOrder) + if (Progres0?.setStudent) { await - Payment0. - setOrder(relatedOrder0); + Progres0. + setStudent(relatedStudent0); } - const relatedOrder1 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Payment1 = await Payments.findOne({ + const Progres1 = await Progress.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Payment1?.setOrder) + if (Progres1?.setStudent) { await - Payment1. - setOrder(relatedOrder1); + Progres1. + setStudent(relatedStudent1); } - const relatedOrder2 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Payment2 = await Payments.findOne({ + const Progres2 = await Progress.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Payment2?.setOrder) + if (Progres2?.setStudent) { await - Payment2. - setOrder(relatedOrder2); + Progres2. + setStudent(relatedStudent2); } - const relatedOrder3 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedStudent3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), }); - const Payment3 = await Payments.findOne({ + const Progres3 = await Progress.findOne({ order: [['id', 'ASC']], offset: 3 }); - if (Payment3?.setOrder) + if (Progres3?.setStudent) { await - Payment3. - setOrder(relatedOrder3); + Progres3. + setStudent(relatedStudent3); } - const relatedOrder4 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + } + + + + + async function associateProgresWithLesson() { + + const relatedLesson0 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), }); - const Payment4 = await Payments.findOne({ + const Progres0 = await Progress.findOne({ order: [['id', 'ASC']], - offset: 4 + offset: 0 }); - if (Payment4?.setOrder) + if (Progres0?.setLesson) { await - Payment4. - setOrder(relatedOrder4); + Progres0. + setLesson(relatedLesson0); + } + + const relatedLesson1 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const Progres1 = await Progress.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Progres1?.setLesson) + { + await + Progres1. + setLesson(relatedLesson1); + } + + const relatedLesson2 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const Progres2 = await Progress.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Progres2?.setLesson) + { + await + Progres2. + setLesson(relatedLesson2); + } + + const relatedLesson3 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const Progres3 = await Progress.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Progres3?.setLesson) + { + await + Progres3. + setLesson(relatedLesson3); + } + + } + + + + + + + + + + + + + + + + + + + async function associateQuizzeWithLesson() { + + const relatedLesson0 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const Quizze0 = await Quizzes.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Quizze0?.setLesson) + { + await + Quizze0. + setLesson(relatedLesson0); + } + + const relatedLesson1 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const Quizze1 = await Quizzes.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Quizze1?.setLesson) + { + await + Quizze1. + setLesson(relatedLesson1); + } + + const relatedLesson2 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const Quizze2 = await Quizzes.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Quizze2?.setLesson) + { + await + Quizze2. + setLesson(relatedLesson2); + } + + const relatedLesson3 = await Lessons.findOne({ + offset: Math.floor(Math.random() * (await Lessons.count())), + }); + const Quizze3 = await Quizzes.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Quizze3?.setLesson) + { + await + Quizze3. + setLesson(relatedLesson3); } } @@ -2401,76 +2546,62 @@ const ShipmentsData = [ - async function associateShipmentWithOrder() { + async function associateQuizQuestionWithQuiz() { - const relatedOrder0 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedQuiz0 = await Quizzes.findOne({ + offset: Math.floor(Math.random() * (await Quizzes.count())), }); - const Shipment0 = await Shipments.findOne({ + const QuizQuestion0 = await QuizQuestions.findOne({ order: [['id', 'ASC']], offset: 0 }); - if (Shipment0?.setOrder) + if (QuizQuestion0?.setQuiz) { await - Shipment0. - setOrder(relatedOrder0); + QuizQuestion0. + setQuiz(relatedQuiz0); } - const relatedOrder1 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedQuiz1 = await Quizzes.findOne({ + offset: Math.floor(Math.random() * (await Quizzes.count())), }); - const Shipment1 = await Shipments.findOne({ + const QuizQuestion1 = await QuizQuestions.findOne({ order: [['id', 'ASC']], offset: 1 }); - if (Shipment1?.setOrder) + if (QuizQuestion1?.setQuiz) { await - Shipment1. - setOrder(relatedOrder1); + QuizQuestion1. + setQuiz(relatedQuiz1); } - const relatedOrder2 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedQuiz2 = await Quizzes.findOne({ + offset: Math.floor(Math.random() * (await Quizzes.count())), }); - const Shipment2 = await Shipments.findOne({ + const QuizQuestion2 = await QuizQuestions.findOne({ order: [['id', 'ASC']], offset: 2 }); - if (Shipment2?.setOrder) + if (QuizQuestion2?.setQuiz) { await - Shipment2. - setOrder(relatedOrder2); + QuizQuestion2. + setQuiz(relatedQuiz2); } - const relatedOrder3 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), + const relatedQuiz3 = await Quizzes.findOne({ + offset: Math.floor(Math.random() * (await Quizzes.count())), }); - const Shipment3 = await Shipments.findOne({ + const QuizQuestion3 = await QuizQuestions.findOne({ order: [['id', 'ASC']], offset: 3 }); - if (Shipment3?.setOrder) + if (QuizQuestion3?.setQuiz) { await - Shipment3. - setOrder(relatedOrder3); - } - - const relatedOrder4 = await Orders.findOne({ - offset: Math.floor(Math.random() * (await Orders.count())), - }); - const Shipment4 = await Shipments.findOne({ - order: [['id', 'ASC']], - offset: 4 - }); - if (Shipment4?.setOrder) - { - await - Shipment4. - setOrder(relatedOrder4); + QuizQuestion3. + setQuiz(relatedQuiz3); } } @@ -2484,6 +2615,215 @@ const ShipmentsData = [ + + + + + + + async function associateAnnouncementWithCourse() { + + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Announcement0 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Announcement0?.setCourse) + { + await + Announcement0. + setCourse(relatedCourse0); + } + + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Announcement1 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Announcement1?.setCourse) + { + await + Announcement1. + setCourse(relatedCourse1); + } + + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Announcement2 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Announcement2?.setCourse) + { + await + Announcement2. + setCourse(relatedCourse2); + } + + const relatedCourse3 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Announcement3 = await Announcements.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Announcement3?.setCourse) + { + await + Announcement3. + setCourse(relatedCourse3); + } + + } + + + + + + + + + + + + + + + + + + + async function associateCertificateWithStudent() { + + const relatedStudent0 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Certificate0 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Certificate0?.setStudent) + { + await + Certificate0. + setStudent(relatedStudent0); + } + + const relatedStudent1 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Certificate1 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Certificate1?.setStudent) + { + await + Certificate1. + setStudent(relatedStudent1); + } + + const relatedStudent2 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Certificate2 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Certificate2?.setStudent) + { + await + Certificate2. + setStudent(relatedStudent2); + } + + const relatedStudent3 = await Users.findOne({ + offset: Math.floor(Math.random() * (await Users.count())), + }); + const Certificate3 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Certificate3?.setStudent) + { + await + Certificate3. + setStudent(relatedStudent3); + } + + } + + + + + async function associateCertificateWithCourse() { + + const relatedCourse0 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Certificate0 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 0 + }); + if (Certificate0?.setCourse) + { + await + Certificate0. + setCourse(relatedCourse0); + } + + const relatedCourse1 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Certificate1 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 1 + }); + if (Certificate1?.setCourse) + { + await + Certificate1. + setCourse(relatedCourse1); + } + + const relatedCourse2 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Certificate2 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 2 + }); + if (Certificate2?.setCourse) + { + await + Certificate2. + setCourse(relatedCourse2); + } + + const relatedCourse3 = await Courses.findOne({ + offset: Math.floor(Math.random() * (await Courses.count())), + }); + const Certificate3 = await Certificates.findOne({ + order: [['id', 'ASC']], + offset: 3 + }); + if (Certificate3?.setCourse) + { + await + Certificate3. + setCourse(relatedCourse3); + } + + } + + + + @@ -2497,37 +2837,47 @@ module.exports = { - await Products.bulkCreate(ProductsData); + await CourseCategories.bulkCreate(CourseCategoriesData); - await Categories.bulkCreate(CategoriesData); + await Courses.bulkCreate(CoursesData); - await Customers.bulkCreate(CustomersData); + await Lessons.bulkCreate(LessonsData); - await Orders.bulkCreate(OrdersData); + await Enrollments.bulkCreate(EnrollmentsData); - await OrderItems.bulkCreate(OrderItemsData); + await Progress.bulkCreate(ProgressData); - await Payments.bulkCreate(PaymentsData); + await Quizzes.bulkCreate(QuizzesData); - await Shipments.bulkCreate(ShipmentsData); + await QuizQuestions.bulkCreate(QuizQuestionsData); + + + + + await Announcements.bulkCreate(AnnouncementsData); + + + + + await Certificates.bulkCreate(CertificatesData); await Promise.all([ @@ -2577,31 +2927,6 @@ module.exports = { - - - - - - - - - await associateProductWithCategory(), - - - - - - - - - - - - - - await associateCategoryWithParent(), - - @@ -2612,21 +2937,13 @@ module.exports = { - - - - - - - - - - + + await associateCoursWithInstructor(), - await associateOrderWithCustomer(), + await associateCoursWithCategory(), @@ -2650,12 +2967,15 @@ module.exports = { - await associateOrderItemWithOrder(), + await associateLessonWithCourse(), - - await associateOrderItemWithProduct(), + + + + + @@ -2671,7 +2991,53 @@ module.exports = { - await associatePaymentWithOrder(), + await associateEnrollmentWithStudent(), + + + + + await associateEnrollmentWithCourse(), + + + + + + + + + + + + + + + + + + await associateProgresWithStudent(), + + + + + await associateProgresWithLesson(), + + + + + + + + + + + + + + + + + + await associateQuizzeWithLesson(), @@ -2687,7 +3053,7 @@ module.exports = { - await associateShipmentWithOrder(), + await associateQuizQuestionWithQuiz(), @@ -2698,6 +3064,39 @@ module.exports = { + + + + + + await associateAnnouncementWithCourse(), + + + + + + + + + + + + + + + + + + await associateCertificateWithStudent(), + + + + + await associateCertificateWithCourse(), + + + + @@ -2712,25 +3111,31 @@ module.exports = { - await queryInterface.bulkDelete('products', null, {}); + await queryInterface.bulkDelete('course_categories', null, {}); - await queryInterface.bulkDelete('categories', null, {}); + await queryInterface.bulkDelete('courses', null, {}); - await queryInterface.bulkDelete('customers', null, {}); + await queryInterface.bulkDelete('lessons', null, {}); - await queryInterface.bulkDelete('orders', null, {}); + await queryInterface.bulkDelete('enrollments', null, {}); - await queryInterface.bulkDelete('order_items', null, {}); + await queryInterface.bulkDelete('progress', null, {}); - await queryInterface.bulkDelete('payments', null, {}); + await queryInterface.bulkDelete('quizzes', null, {}); - await queryInterface.bulkDelete('shipments', null, {}); + await queryInterface.bulkDelete('quiz_questions', null, {}); + + + await queryInterface.bulkDelete('announcements', null, {}); + + + await queryInterface.bulkDelete('certificates', null, {}); }, diff --git a/backend/src/index.js b/backend/src/index.js index 8f3a447..a02b54d 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -26,19 +26,23 @@ const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); -const productsRoutes = require('./routes/products'); +const course_categoriesRoutes = require('./routes/course_categories'); -const categoriesRoutes = require('./routes/categories'); +const coursesRoutes = require('./routes/courses'); -const customersRoutes = require('./routes/customers'); +const lessonsRoutes = require('./routes/lessons'); -const ordersRoutes = require('./routes/orders'); +const enrollmentsRoutes = require('./routes/enrollments'); -const order_itemsRoutes = require('./routes/order_items'); +const progressRoutes = require('./routes/progress'); -const paymentsRoutes = require('./routes/payments'); +const quizzesRoutes = require('./routes/quizzes'); -const shipmentsRoutes = require('./routes/shipments'); +const quiz_questionsRoutes = require('./routes/quiz_questions'); + +const announcementsRoutes = require('./routes/announcements'); + +const certificatesRoutes = require('./routes/certificates'); const getBaseUrl = (url) => { @@ -51,8 +55,8 @@ const options = { openapi: "3.0.0", info: { version: "1.0.0", - title: "Store Operations Manager", - description: "Store Operations Manager Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.", + title: "CourseFlow LMS", + description: "CourseFlow LMS Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.", }, servers: [ { @@ -104,19 +108,23 @@ app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoute app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes); -app.use('/api/products', passport.authenticate('jwt', {session: false}), productsRoutes); +app.use('/api/course_categories', passport.authenticate('jwt', {session: false}), course_categoriesRoutes); -app.use('/api/categories', passport.authenticate('jwt', {session: false}), categoriesRoutes); +app.use('/api/courses', passport.authenticate('jwt', {session: false}), coursesRoutes); -app.use('/api/customers', passport.authenticate('jwt', {session: false}), customersRoutes); +app.use('/api/lessons', passport.authenticate('jwt', {session: false}), lessonsRoutes); -app.use('/api/orders', passport.authenticate('jwt', {session: false}), ordersRoutes); +app.use('/api/enrollments', passport.authenticate('jwt', {session: false}), enrollmentsRoutes); -app.use('/api/order_items', passport.authenticate('jwt', {session: false}), order_itemsRoutes); +app.use('/api/progress', passport.authenticate('jwt', {session: false}), progressRoutes); -app.use('/api/payments', passport.authenticate('jwt', {session: false}), paymentsRoutes); +app.use('/api/quizzes', passport.authenticate('jwt', {session: false}), quizzesRoutes); -app.use('/api/shipments', passport.authenticate('jwt', {session: false}), shipmentsRoutes); +app.use('/api/quiz_questions', passport.authenticate('jwt', {session: false}), quiz_questionsRoutes); + +app.use('/api/announcements', passport.authenticate('jwt', {session: false}), announcementsRoutes); + +app.use('/api/certificates', passport.authenticate('jwt', {session: false}), certificatesRoutes); app.use( '/api/openai', diff --git a/backend/src/routes/categories.js b/backend/src/routes/announcements.js similarity index 76% rename from backend/src/routes/categories.js rename to backend/src/routes/announcements.js index 95b2f47..b626482 100644 --- a/backend/src/routes/categories.js +++ b/backend/src/routes/announcements.js @@ -1,8 +1,8 @@ const express = require('express'); -const CategoriesService = require('../services/categories'); -const CategoriesDBApi = require('../db/api/categories'); +const AnnouncementsService = require('../services/announcements'); +const AnnouncementsDBApi = require('../db/api/announcements'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,23 +15,23 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('categories')); +router.use(checkCrudPermissions('announcements')); /** * @swagger * components: * schemas: - * Categories: + * Announcements: * type: object * properties: - * name: + * title: * type: string - * default: name - * description: + * default: title + * content: * type: string - * default: description + * default: content @@ -40,17 +40,17 @@ router.use(checkCrudPermissions('categories')); /** * @swagger * tags: - * name: Categories - * description: The Categories managing API + * name: Announcements + * description: The Announcements managing API */ /** * @swagger -* /api/categories: +* /api/announcements: * post: * security: * - bearerAuth: [] -* tags: [Categories] +* tags: [Announcements] * summary: Add new item * description: Add new item * requestBody: @@ -62,14 +62,14 @@ router.use(checkCrudPermissions('categories')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Categories" +* $ref: "#/components/schemas/Announcements" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Categories" +* $ref: "#/components/schemas/Announcements" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -80,7 +80,7 @@ router.use(checkCrudPermissions('categories')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await CategoriesService.create(req.body.data, req.currentUser, true, link.host); + await AnnouncementsService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -91,7 +91,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Categories] + * tags: [Announcements] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -104,14 +104,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -123,18 +123,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 CategoriesService.bulkImport(req, res, true, link.host); + await AnnouncementsService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/categories/{id}: + * /api/announcements/{id}: * put: * security: * - bearerAuth: [] - * tags: [Categories] + * tags: [Announcements] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -157,7 +157,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * required: * - id * responses: @@ -166,7 +166,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 400: * description: Invalid ID supplied * 401: @@ -177,18 +177,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await CategoriesService.update(req.body.data, req.body.id, req.currentUser); + await AnnouncementsService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/categories/{id}: + * /api/announcements/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Categories] + * tags: [Announcements] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -204,7 +204,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 400: * description: Invalid ID supplied * 401: @@ -215,18 +215,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await CategoriesService.remove(req.params.id, req.currentUser); + await AnnouncementsService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/categories/deleteByIds: + * /api/announcements/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Categories] + * tags: [Announcements] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -244,7 +244,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -253,29 +253,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await CategoriesService.deleteByIds(req.body.data, req.currentUser); + await AnnouncementsService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/categories: + * /api/announcements: * get: * security: * - bearerAuth: [] - * tags: [Categories] - * summary: Get all categories - * description: Get all categories + * tags: [Announcements] + * summary: Get all announcements + * description: Get all announcements * responses: * 200: - * description: Categories list successfully received + * description: Announcements list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -287,14 +287,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await CategoriesDBApi.findAll( + const payload = await AnnouncementsDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','name','description', + const fields = ['id','title','content', - + 'published_at', ]; const opts = { fields }; try { @@ -313,22 +313,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/categories/count: + * /api/announcements/count: * get: * security: * - bearerAuth: [] - * tags: [Categories] - * summary: Count all categories - * description: Count all categories + * tags: [Announcements] + * summary: Count all announcements + * description: Count all announcements * responses: * 200: - * description: Categories count successfully received + * description: Announcements count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -339,7 +339,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await CategoriesDBApi.findAll( + const payload = await AnnouncementsDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -350,22 +350,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/categories/autocomplete: + * /api/announcements/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Categories] - * summary: Find all categories that match search criteria - * description: Find all categories that match search criteria + * tags: [Announcements] + * summary: Find all announcements that match search criteria + * description: Find all announcements that match search criteria * responses: * 200: - * description: Categories list successfully received + * description: Announcements list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -375,7 +375,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await CategoriesDBApi.findAllAutocomplete( + const payload = await AnnouncementsDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -387,11 +387,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/categories/{id}: + * /api/announcements/{id}: * get: * security: * - bearerAuth: [] - * tags: [Categories] + * tags: [Announcements] * summary: Get selected item * description: Get selected item * parameters: @@ -407,7 +407,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Categories" + * $ref: "#/components/schemas/Announcements" * 400: * description: Invalid ID supplied * 401: @@ -418,7 +418,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await CategoriesDBApi.findBy( + const payload = await AnnouncementsDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/shipments.js b/backend/src/routes/certificates.js similarity index 76% rename from backend/src/routes/shipments.js rename to backend/src/routes/certificates.js index 338f742..663374d 100644 --- a/backend/src/routes/shipments.js +++ b/backend/src/routes/certificates.js @@ -1,8 +1,8 @@ const express = require('express'); -const ShipmentsService = require('../services/shipments'); -const ShipmentsDBApi = require('../db/api/shipments'); +const CertificatesService = require('../services/certificates'); +const CertificatesDBApi = require('../db/api/certificates'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,43 +15,39 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('shipments')); +router.use(checkCrudPermissions('certificates')); /** * @swagger * components: * schemas: - * Shipments: + * Certificates: * type: object * properties: - * carrier: + * serial: * type: string - * default: carrier - * tracking_number: - * type: string - * default: tracking_number + * default: serial - * */ /** * @swagger * tags: - * name: Shipments - * description: The Shipments managing API + * name: Certificates + * description: The Certificates managing API */ /** * @swagger -* /api/shipments: +* /api/certificates: * post: * security: * - bearerAuth: [] -* tags: [Shipments] +* tags: [Certificates] * summary: Add new item * description: Add new item * requestBody: @@ -63,14 +59,14 @@ router.use(checkCrudPermissions('shipments')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Shipments" +* $ref: "#/components/schemas/Certificates" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Shipments" +* $ref: "#/components/schemas/Certificates" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -81,7 +77,7 @@ router.use(checkCrudPermissions('shipments')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await ShipmentsService.create(req.body.data, req.currentUser, true, link.host); + await CertificatesService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -92,7 +88,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Shipments] + * tags: [Certificates] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -105,14 +101,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -124,18 +120,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 ShipmentsService.bulkImport(req, res, true, link.host); + await CertificatesService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/shipments/{id}: + * /api/certificates/{id}: * put: * security: * - bearerAuth: [] - * tags: [Shipments] + * tags: [Certificates] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -158,7 +154,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * required: * - id * responses: @@ -167,7 +163,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 400: * description: Invalid ID supplied * 401: @@ -178,18 +174,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await ShipmentsService.update(req.body.data, req.body.id, req.currentUser); + await CertificatesService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/shipments/{id}: + * /api/certificates/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Shipments] + * tags: [Certificates] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -205,7 +201,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 400: * description: Invalid ID supplied * 401: @@ -216,18 +212,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await ShipmentsService.remove(req.params.id, req.currentUser); + await CertificatesService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/shipments/deleteByIds: + * /api/certificates/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Shipments] + * tags: [Certificates] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -245,7 +241,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -254,29 +250,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await ShipmentsService.deleteByIds(req.body.data, req.currentUser); + await CertificatesService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/shipments: + * /api/certificates: * get: * security: * - bearerAuth: [] - * tags: [Shipments] - * summary: Get all shipments - * description: Get all shipments + * tags: [Certificates] + * summary: Get all certificates + * description: Get all certificates * responses: * 200: - * description: Shipments list successfully received + * description: Certificates list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -288,14 +284,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await ShipmentsDBApi.findAll( + const payload = await CertificatesDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','carrier','tracking_number', + const fields = ['id','serial', - 'shipped_at','delivered_at', + 'issued_at', ]; const opts = { fields }; try { @@ -314,22 +310,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/shipments/count: + * /api/certificates/count: * get: * security: * - bearerAuth: [] - * tags: [Shipments] - * summary: Count all shipments - * description: Count all shipments + * tags: [Certificates] + * summary: Count all certificates + * description: Count all certificates * responses: * 200: - * description: Shipments count successfully received + * description: Certificates count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -340,7 +336,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await ShipmentsDBApi.findAll( + const payload = await CertificatesDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -351,22 +347,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/shipments/autocomplete: + * /api/certificates/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Shipments] - * summary: Find all shipments that match search criteria - * description: Find all shipments that match search criteria + * tags: [Certificates] + * summary: Find all certificates that match search criteria + * description: Find all certificates that match search criteria * responses: * 200: - * description: Shipments list successfully received + * description: Certificates list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -376,7 +372,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await ShipmentsDBApi.findAllAutocomplete( + const payload = await CertificatesDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -388,11 +384,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/shipments/{id}: + * /api/certificates/{id}: * get: * security: * - bearerAuth: [] - * tags: [Shipments] + * tags: [Certificates] * summary: Get selected item * description: Get selected item * parameters: @@ -408,7 +404,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Shipments" + * $ref: "#/components/schemas/Certificates" * 400: * description: Invalid ID supplied * 401: @@ -419,7 +415,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await ShipmentsDBApi.findBy( + const payload = await CertificatesDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/course_categories.js b/backend/src/routes/course_categories.js new file mode 100644 index 0000000..1b64dcb --- /dev/null +++ b/backend/src/routes/course_categories.js @@ -0,0 +1,435 @@ + +const express = require('express'); + +const Course_categoriesService = require('../services/course_categories'); +const Course_categoriesDBApi = require('../db/api/course_categories'); +const wrapAsync = require('../helpers').wrapAsync; + + +const router = express.Router(); + +const { parse } = require('json2csv'); + + +const { + checkCrudPermissions, +} = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('course_categories')); + + +/** + * @swagger + * components: + * schemas: + * Course_categories: + * type: object + * properties: + + * name: + * type: string + * default: name + * slug: + * type: string + * default: slug + * description: + * type: string + * default: description + + + + */ + +/** + * @swagger + * tags: + * name: Course_categories + * description: The Course_categories managing API + */ + +/** +* @swagger +* /api/course_categories: +* post: +* security: +* - bearerAuth: [] +* tags: [Course_categories] +* 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/Course_categories" +* responses: +* 200: +* description: The item was successfully added +* content: +* application/json: +* schema: +* $ref: "#/components/schemas/Course_categories" +* 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 Course_categoriesService.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: [Course_categories] + * 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/Course_categories" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Course_categories" + * 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 Course_categoriesService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/course_categories/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Course_categories] + * 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/Course_categories" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Course_categories" + * 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 Course_categoriesService.update(req.body.data, req.body.id, req.currentUser); + const payload = true; + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/course_categories/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Course_categories] + * 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/Course_categories" + * 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 Course_categoriesService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/course_categories/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Course_categories] + * 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/Course_categories" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post('/deleteByIds', wrapAsync(async (req, res) => { + await Course_categoriesService.deleteByIds(req.body.data, req.currentUser); + const payload = true; + res.status(200).send(payload); + })); + +/** + * @swagger + * /api/course_categories: + * get: + * security: + * - bearerAuth: [] + * tags: [Course_categories] + * summary: Get all course_categories + * description: Get all course_categories + * responses: + * 200: + * description: Course_categories list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Course_categories" + * 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 Course_categoriesDBApi.findAll( + req.query, { currentUser } + ); + if (filetype && filetype === 'csv') { + const fields = ['id','name','slug','description', + + + + ]; + const opts = { fields }; + try { + const csv = parse(payload.rows, opts); + res.status(200).attachment(csv); + res.send(csv) + + } catch (err) { + console.error(err); + } + } else { + res.status(200).send(payload); + } + +})); + +/** + * @swagger + * /api/course_categories/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Course_categories] + * summary: Count all course_categories + * description: Count all course_categories + * responses: + * 200: + * description: Course_categories count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Course_categories" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/count', wrapAsync(async (req, res) => { + + const currentUser = req.currentUser; + const payload = await Course_categoriesDBApi.findAll( + req.query, + null, + { countOnly: true, currentUser } + ); + + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/course_categories/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Course_categories] + * summary: Find all course_categories that match search criteria + * description: Find all course_categories that match search criteria + * responses: + * 200: + * description: Course_categories list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Course_categories" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + + const payload = await Course_categoriesDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/course_categories/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Course_categories] + * 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/Course_categories" + * 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 Course_categoriesDBApi.findBy( + { id: req.params.id }, + ); + + + + res.status(200).send(payload); +})); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/routes/customers.js b/backend/src/routes/courses.js similarity index 75% rename from backend/src/routes/customers.js rename to backend/src/routes/courses.js index 9c02007..bd3a548 100644 --- a/backend/src/routes/customers.js +++ b/backend/src/routes/courses.js @@ -1,8 +1,8 @@ const express = require('express'); -const CustomersService = require('../services/customers'); -const CustomersDBApi = require('../db/api/customers'); +const CoursesService = require('../services/courses'); +const CoursesDBApi = require('../db/api/courses'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,54 +15,55 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('customers')); +router.use(checkCrudPermissions('courses')); /** * @swagger * components: * schemas: - * Customers: + * Courses: * type: object * properties: - * name: + * title: * type: string - * default: name - * email: + * default: title + * short_description: * type: string - * default: email - * phone: + * default: short_description + * description: * type: string - * default: phone - * address: + * default: description + * language: * type: string - * default: address - * notes: - * type: string - * default: notes - * tax_number: - * type: string - * default: tax_number + * default: language + * duration: + * type: integer + * format: int64 + * price: + * type: integer + * format: int64 + * */ /** * @swagger * tags: - * name: Customers - * description: The Customers managing API + * name: Courses + * description: The Courses managing API */ /** * @swagger -* /api/customers: +* /api/courses: * post: * security: * - bearerAuth: [] -* tags: [Customers] +* tags: [Courses] * summary: Add new item * description: Add new item * requestBody: @@ -74,14 +75,14 @@ router.use(checkCrudPermissions('customers')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Customers" +* $ref: "#/components/schemas/Courses" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Customers" +* $ref: "#/components/schemas/Courses" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -92,7 +93,7 @@ router.use(checkCrudPermissions('customers')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await CustomersService.create(req.body.data, req.currentUser, true, link.host); + await CoursesService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -103,7 +104,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Customers] + * tags: [Courses] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -116,14 +117,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -135,18 +136,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 CustomersService.bulkImport(req, res, true, link.host); + await CoursesService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/customers/{id}: + * /api/courses/{id}: * put: * security: * - bearerAuth: [] - * tags: [Customers] + * tags: [Courses] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -169,7 +170,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * required: * - id * responses: @@ -178,7 +179,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 400: * description: Invalid ID supplied * 401: @@ -189,18 +190,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await CustomersService.update(req.body.data, req.body.id, req.currentUser); + await CoursesService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/customers/{id}: + * /api/courses/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Customers] + * tags: [Courses] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -216,7 +217,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 400: * description: Invalid ID supplied * 401: @@ -227,18 +228,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await CustomersService.remove(req.params.id, req.currentUser); + await CoursesService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/customers/deleteByIds: + * /api/courses/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Customers] + * tags: [Courses] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -256,7 +257,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -265,29 +266,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await CustomersService.deleteByIds(req.body.data, req.currentUser); + await CoursesService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/customers: + * /api/courses: * get: * security: * - bearerAuth: [] - * tags: [Customers] - * summary: Get all customers - * description: Get all customers + * tags: [Courses] + * summary: Get all courses + * description: Get all courses * responses: * 200: - * description: Customers list successfully received + * description: Courses list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -299,14 +300,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await CustomersDBApi.findAll( + const payload = await CoursesDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','name','email','phone','address','notes','tax_number', - - - + const fields = ['id','title','short_description','description','language', + 'duration', + 'price', + 'published_at', ]; const opts = { fields }; try { @@ -325,22 +326,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/customers/count: + * /api/courses/count: * get: * security: * - bearerAuth: [] - * tags: [Customers] - * summary: Count all customers - * description: Count all customers + * tags: [Courses] + * summary: Count all courses + * description: Count all courses * responses: * 200: - * description: Customers count successfully received + * description: Courses count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -351,7 +352,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await CustomersDBApi.findAll( + const payload = await CoursesDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -362,22 +363,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/customers/autocomplete: + * /api/courses/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Customers] - * summary: Find all customers that match search criteria - * description: Find all customers that match search criteria + * tags: [Courses] + * summary: Find all courses that match search criteria + * description: Find all courses that match search criteria * responses: * 200: - * description: Customers list successfully received + * description: Courses list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -387,7 +388,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await CustomersDBApi.findAllAutocomplete( + const payload = await CoursesDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -399,11 +400,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/customers/{id}: + * /api/courses/{id}: * get: * security: * - bearerAuth: [] - * tags: [Customers] + * tags: [Courses] * summary: Get selected item * description: Get selected item * parameters: @@ -419,7 +420,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Customers" + * $ref: "#/components/schemas/Courses" * 400: * description: Invalid ID supplied * 401: @@ -430,7 +431,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await CustomersDBApi.findBy( + const payload = await CoursesDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/order_items.js b/backend/src/routes/enrollments.js similarity index 76% rename from backend/src/routes/order_items.js rename to backend/src/routes/enrollments.js index a842642..b62c42a 100644 --- a/backend/src/routes/order_items.js +++ b/backend/src/routes/enrollments.js @@ -1,8 +1,8 @@ const express = require('express'); -const Order_itemsService = require('../services/order_items'); -const Order_itemsDBApi = require('../db/api/order_items'); +const EnrollmentsService = require('../services/enrollments'); +const EnrollmentsDBApi = require('../db/api/enrollments'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,48 +15,43 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('order_items')); +router.use(checkCrudPermissions('enrollments')); /** * @swagger * components: * schemas: - * Order_items: + * Enrollments: * type: object * properties: - * name: + * enrollment_code: * type: string - * default: name + * default: enrollment_code - * quantity: - * type: integer - * format: int64 - - * unit_price: - * type: integer - * format: int64 - * total_price: + + * progress_percent: * type: integer * format: int64 + * */ /** * @swagger * tags: - * name: Order_items - * description: The Order_items managing API + * name: Enrollments + * description: The Enrollments managing API */ /** * @swagger -* /api/order_items: +* /api/enrollments: * post: * security: * - bearerAuth: [] -* tags: [Order_items] +* tags: [Enrollments] * summary: Add new item * description: Add new item * requestBody: @@ -68,14 +63,14 @@ router.use(checkCrudPermissions('order_items')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Order_items" +* $ref: "#/components/schemas/Enrollments" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Order_items" +* $ref: "#/components/schemas/Enrollments" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -86,7 +81,7 @@ router.use(checkCrudPermissions('order_items')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await Order_itemsService.create(req.body.data, req.currentUser, true, link.host); + await EnrollmentsService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -97,7 +92,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Order_items] + * tags: [Enrollments] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -110,14 +105,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -129,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 Order_itemsService.bulkImport(req, res, true, link.host); + await EnrollmentsService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/order_items/{id}: + * /api/enrollments/{id}: * put: * security: * - bearerAuth: [] - * tags: [Order_items] + * tags: [Enrollments] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -163,7 +158,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * required: * - id * responses: @@ -172,7 +167,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 400: * description: Invalid ID supplied * 401: @@ -183,18 +178,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await Order_itemsService.update(req.body.data, req.body.id, req.currentUser); + await EnrollmentsService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/order_items/{id}: + * /api/enrollments/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Order_items] + * tags: [Enrollments] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -210,7 +205,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 400: * description: Invalid ID supplied * 401: @@ -221,18 +216,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await Order_itemsService.remove(req.params.id, req.currentUser); + await EnrollmentsService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/order_items/deleteByIds: + * /api/enrollments/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Order_items] + * tags: [Enrollments] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -250,7 +245,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -259,29 +254,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await Order_itemsService.deleteByIds(req.body.data, req.currentUser); + await EnrollmentsService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/order_items: + * /api/enrollments: * get: * security: * - bearerAuth: [] - * tags: [Order_items] - * summary: Get all order_items - * description: Get all order_items + * tags: [Enrollments] + * summary: Get all enrollments + * description: Get all enrollments * responses: * 200: - * description: Order_items list successfully received + * description: Enrollments list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -293,14 +288,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await Order_itemsDBApi.findAll( + const payload = await EnrollmentsDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','name', - 'quantity', - 'unit_price','total_price', - + const fields = ['id','enrollment_code', + + 'progress_percent', + 'enrolled_at', ]; const opts = { fields }; try { @@ -319,22 +314,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/order_items/count: + * /api/enrollments/count: * get: * security: * - bearerAuth: [] - * tags: [Order_items] - * summary: Count all order_items - * description: Count all order_items + * tags: [Enrollments] + * summary: Count all enrollments + * description: Count all enrollments * responses: * 200: - * description: Order_items count successfully received + * description: Enrollments count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -345,7 +340,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await Order_itemsDBApi.findAll( + const payload = await EnrollmentsDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -356,22 +351,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/order_items/autocomplete: + * /api/enrollments/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Order_items] - * summary: Find all order_items that match search criteria - * description: Find all order_items that match search criteria + * tags: [Enrollments] + * summary: Find all enrollments that match search criteria + * description: Find all enrollments that match search criteria * responses: * 200: - * description: Order_items list successfully received + * description: Enrollments list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -381,7 +376,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await Order_itemsDBApi.findAllAutocomplete( + const payload = await EnrollmentsDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -393,11 +388,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/order_items/{id}: + * /api/enrollments/{id}: * get: * security: * - bearerAuth: [] - * tags: [Order_items] + * tags: [Enrollments] * summary: Get selected item * description: Get selected item * parameters: @@ -413,7 +408,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Order_items" + * $ref: "#/components/schemas/Enrollments" * 400: * description: Invalid ID supplied * 401: @@ -424,7 +419,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await Order_itemsDBApi.findBy( + const payload = await EnrollmentsDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/orders.js b/backend/src/routes/lessons.js similarity index 77% rename from backend/src/routes/orders.js rename to backend/src/routes/lessons.js index 03654f9..70809cb 100644 --- a/backend/src/routes/orders.js +++ b/backend/src/routes/lessons.js @@ -1,8 +1,8 @@ const express = require('express'); -const OrdersService = require('../services/orders'); -const OrdersDBApi = require('../db/api/orders'); +const LessonsService = require('../services/lessons'); +const LessonsDBApi = require('../db/api/lessons'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,47 +15,48 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('orders')); +router.use(checkCrudPermissions('lessons')); /** * @swagger * components: * schemas: - * Orders: + * Lessons: * type: object * properties: - * order_number: + * title: * type: string - * default: order_number - * shipping_address: + * default: title + * content: * type: string - * default: shipping_address + * default: content - - * total: + * order: + * type: integer + * format: int64 + * duration: * type: integer * format: int64 - * - * + */ /** * @swagger * tags: - * name: Orders - * description: The Orders managing API + * name: Lessons + * description: The Lessons managing API */ /** * @swagger -* /api/orders: +* /api/lessons: * post: * security: * - bearerAuth: [] -* tags: [Orders] +* tags: [Lessons] * summary: Add new item * description: Add new item * requestBody: @@ -67,14 +68,14 @@ router.use(checkCrudPermissions('orders')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Orders" +* $ref: "#/components/schemas/Lessons" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Orders" +* $ref: "#/components/schemas/Lessons" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -85,7 +86,7 @@ router.use(checkCrudPermissions('orders')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await OrdersService.create(req.body.data, req.currentUser, true, link.host); + await LessonsService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -96,7 +97,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Orders] + * tags: [Lessons] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -109,14 +110,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -128,18 +129,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 OrdersService.bulkImport(req, res, true, link.host); + await LessonsService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/orders/{id}: + * /api/lessons/{id}: * put: * security: * - bearerAuth: [] - * tags: [Orders] + * tags: [Lessons] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -162,7 +163,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * required: * - id * responses: @@ -171,7 +172,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 400: * description: Invalid ID supplied * 401: @@ -182,18 +183,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await OrdersService.update(req.body.data, req.body.id, req.currentUser); + await LessonsService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/orders/{id}: + * /api/lessons/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Orders] + * tags: [Lessons] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -209,7 +210,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 400: * description: Invalid ID supplied * 401: @@ -220,18 +221,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await OrdersService.remove(req.params.id, req.currentUser); + await LessonsService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/orders/deleteByIds: + * /api/lessons/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Orders] + * tags: [Lessons] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -249,7 +250,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -258,29 +259,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await OrdersService.deleteByIds(req.body.data, req.currentUser); + await LessonsService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/orders: + * /api/lessons: * get: * security: * - bearerAuth: [] - * tags: [Orders] - * summary: Get all orders - * description: Get all orders + * tags: [Lessons] + * summary: Get all lessons + * description: Get all lessons * responses: * 200: - * description: Orders list successfully received + * description: Lessons list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -292,14 +293,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await OrdersDBApi.findAll( + const payload = await LessonsDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','order_number','shipping_address', + const fields = ['id','title','content', + 'order','duration', - 'total', - 'placed_at','shipped_at','delivery_date', + 'start_at','end_at', ]; const opts = { fields }; try { @@ -318,22 +319,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/orders/count: + * /api/lessons/count: * get: * security: * - bearerAuth: [] - * tags: [Orders] - * summary: Count all orders - * description: Count all orders + * tags: [Lessons] + * summary: Count all lessons + * description: Count all lessons * responses: * 200: - * description: Orders count successfully received + * description: Lessons count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -344,7 +345,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await OrdersDBApi.findAll( + const payload = await LessonsDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -355,22 +356,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/orders/autocomplete: + * /api/lessons/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Orders] - * summary: Find all orders that match search criteria - * description: Find all orders that match search criteria + * tags: [Lessons] + * summary: Find all lessons that match search criteria + * description: Find all lessons that match search criteria * responses: * 200: - * description: Orders list successfully received + * description: Lessons list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -380,7 +381,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await OrdersDBApi.findAllAutocomplete( + const payload = await LessonsDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -392,11 +393,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/orders/{id}: + * /api/lessons/{id}: * get: * security: * - bearerAuth: [] - * tags: [Orders] + * tags: [Lessons] * summary: Get selected item * description: Get selected item * parameters: @@ -412,7 +413,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Orders" + * $ref: "#/components/schemas/Lessons" * 400: * description: Invalid ID supplied * 401: @@ -423,7 +424,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await OrdersDBApi.findBy( + const payload = await LessonsDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/payments.js b/backend/src/routes/progress.js similarity index 77% rename from backend/src/routes/payments.js rename to backend/src/routes/progress.js index 9498a9d..4e0d273 100644 --- a/backend/src/routes/payments.js +++ b/backend/src/routes/progress.js @@ -1,8 +1,8 @@ const express = require('express'); -const PaymentsService = require('../services/payments'); -const PaymentsDBApi = require('../db/api/payments'); +const ProgressService = require('../services/progress'); +const ProgressDBApi = require('../db/api/progress'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,44 +15,45 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('payments')); +router.use(checkCrudPermissions('progress')); /** * @swagger * components: * schemas: - * Payments: + * Progress: * type: object * properties: - * reference: + * summary: * type: string - * default: reference + * default: summary + * notes: + * type: string + * default: notes - * amount: + * percent: * type: integer * format: int64 - * - * */ /** * @swagger * tags: - * name: Payments - * description: The Payments managing API + * name: Progress + * description: The Progress managing API */ /** * @swagger -* /api/payments: +* /api/progress: * post: * security: * - bearerAuth: [] -* tags: [Payments] +* tags: [Progress] * summary: Add new item * description: Add new item * requestBody: @@ -64,14 +65,14 @@ router.use(checkCrudPermissions('payments')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Payments" +* $ref: "#/components/schemas/Progress" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Payments" +* $ref: "#/components/schemas/Progress" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -82,7 +83,7 @@ router.use(checkCrudPermissions('payments')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await PaymentsService.create(req.body.data, req.currentUser, true, link.host); + await ProgressService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -93,7 +94,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Payments] + * tags: [Progress] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -106,14 +107,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -125,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 PaymentsService.bulkImport(req, res, true, link.host); + await ProgressService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/payments/{id}: + * /api/progress/{id}: * put: * security: * - bearerAuth: [] - * tags: [Payments] + * tags: [Progress] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -159,7 +160,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * required: * - id * responses: @@ -168,7 +169,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 400: * description: Invalid ID supplied * 401: @@ -179,18 +180,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await PaymentsService.update(req.body.data, req.body.id, req.currentUser); + await ProgressService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/payments/{id}: + * /api/progress/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Payments] + * tags: [Progress] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -206,7 +207,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 400: * description: Invalid ID supplied * 401: @@ -217,18 +218,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await PaymentsService.remove(req.params.id, req.currentUser); + await ProgressService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/payments/deleteByIds: + * /api/progress/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Payments] + * tags: [Progress] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -246,7 +247,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -255,29 +256,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await PaymentsService.deleteByIds(req.body.data, req.currentUser); + await ProgressService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/payments: + * /api/progress: * get: * security: * - bearerAuth: [] - * tags: [Payments] - * summary: Get all payments - * description: Get all payments + * tags: [Progress] + * summary: Get all progress + * description: Get all progress * responses: * 200: - * description: Payments list successfully received + * description: Progress list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -289,14 +290,14 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await PaymentsDBApi.findAll( + const payload = await ProgressDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','reference', + const fields = ['id','summary','notes', - 'amount', - 'paid_at', + 'percent', + 'completed_at', ]; const opts = { fields }; try { @@ -315,22 +316,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/payments/count: + * /api/progress/count: * get: * security: * - bearerAuth: [] - * tags: [Payments] - * summary: Count all payments - * description: Count all payments + * tags: [Progress] + * summary: Count all progress + * description: Count all progress * responses: * 200: - * description: Payments count successfully received + * description: Progress count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -341,7 +342,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await PaymentsDBApi.findAll( + const payload = await ProgressDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -352,22 +353,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/payments/autocomplete: + * /api/progress/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Payments] - * summary: Find all payments that match search criteria - * description: Find all payments that match search criteria + * tags: [Progress] + * summary: Find all progress that match search criteria + * description: Find all progress that match search criteria * responses: * 200: - * description: Payments list successfully received + * description: Progress list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -377,7 +378,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await PaymentsDBApi.findAllAutocomplete( + const payload = await ProgressDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -389,11 +390,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/payments/{id}: + * /api/progress/{id}: * get: * security: * - bearerAuth: [] - * tags: [Payments] + * tags: [Progress] * summary: Get selected item * description: Get selected item * parameters: @@ -409,7 +410,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Payments" + * $ref: "#/components/schemas/Progress" * 400: * description: Invalid ID supplied * 401: @@ -420,7 +421,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await PaymentsDBApi.findBy( + const payload = await ProgressDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/routes/quiz_questions.js b/backend/src/routes/quiz_questions.js new file mode 100644 index 0000000..41ab96f --- /dev/null +++ b/backend/src/routes/quiz_questions.js @@ -0,0 +1,436 @@ + +const express = require('express'); + +const Quiz_questionsService = require('../services/quiz_questions'); +const Quiz_questionsDBApi = require('../db/api/quiz_questions'); +const wrapAsync = require('../helpers').wrapAsync; + + +const router = express.Router(); + +const { parse } = require('json2csv'); + + +const { + checkCrudPermissions, +} = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('quiz_questions')); + + +/** + * @swagger + * components: + * schemas: + * Quiz_questions: + * type: object + * properties: + + * question: + * type: string + * default: question + * choices: + * type: string + * default: choices + * answer: + * type: string + * default: answer + + + + * + */ + +/** + * @swagger + * tags: + * name: Quiz_questions + * description: The Quiz_questions managing API + */ + +/** +* @swagger +* /api/quiz_questions: +* post: +* security: +* - bearerAuth: [] +* tags: [Quiz_questions] +* 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/Quiz_questions" +* responses: +* 200: +* description: The item was successfully added +* content: +* application/json: +* schema: +* $ref: "#/components/schemas/Quiz_questions" +* 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 Quiz_questionsService.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: [Quiz_questions] + * 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/Quiz_questions" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Quiz_questions" + * 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 Quiz_questionsService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/quiz_questions/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Quiz_questions] + * 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/Quiz_questions" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Quiz_questions" + * 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 Quiz_questionsService.update(req.body.data, req.body.id, req.currentUser); + const payload = true; + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/quiz_questions/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Quiz_questions] + * 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/Quiz_questions" + * 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 Quiz_questionsService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/quiz_questions/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Quiz_questions] + * 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/Quiz_questions" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post('/deleteByIds', wrapAsync(async (req, res) => { + await Quiz_questionsService.deleteByIds(req.body.data, req.currentUser); + const payload = true; + res.status(200).send(payload); + })); + +/** + * @swagger + * /api/quiz_questions: + * get: + * security: + * - bearerAuth: [] + * tags: [Quiz_questions] + * summary: Get all quiz_questions + * description: Get all quiz_questions + * responses: + * 200: + * description: Quiz_questions list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Quiz_questions" + * 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 Quiz_questionsDBApi.findAll( + req.query, { currentUser } + ); + if (filetype && filetype === 'csv') { + const fields = ['id','question','choices','answer', + + + + ]; + 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/quiz_questions/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Quiz_questions] + * summary: Count all quiz_questions + * description: Count all quiz_questions + * responses: + * 200: + * description: Quiz_questions count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Quiz_questions" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/count', wrapAsync(async (req, res) => { + + const currentUser = req.currentUser; + const payload = await Quiz_questionsDBApi.findAll( + req.query, + null, + { countOnly: true, currentUser } + ); + + res.status(200).send(payload); +})); + +/** + * @swagger + * /api/quiz_questions/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Quiz_questions] + * summary: Find all quiz_questions that match search criteria + * description: Find all quiz_questions that match search criteria + * responses: + * 200: + * description: Quiz_questions list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Quiz_questions" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + + const payload = await Quiz_questionsDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/quiz_questions/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Quiz_questions] + * 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/Quiz_questions" + * 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 Quiz_questionsDBApi.findBy( + { id: req.params.id }, + ); + + + + res.status(200).send(payload); +})); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/routes/products.js b/backend/src/routes/quizzes.js similarity index 77% rename from backend/src/routes/products.js rename to backend/src/routes/quizzes.js index bdcd7fa..db91623 100644 --- a/backend/src/routes/products.js +++ b/backend/src/routes/quizzes.js @@ -1,8 +1,8 @@ const express = require('express'); -const ProductsService = require('../services/products'); -const ProductsDBApi = require('../db/api/products'); +const QuizzesService = require('../services/quizzes'); +const QuizzesDBApi = require('../db/api/quizzes'); const wrapAsync = require('../helpers').wrapAsync; @@ -15,52 +15,48 @@ const { checkCrudPermissions, } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('products')); +router.use(checkCrudPermissions('quizzes')); /** * @swagger * components: * schemas: - * Products: + * Quizzes: * type: object * properties: - * sku: + * title: * type: string - * default: sku - * name: - * type: string - * default: name + * default: title * description: * type: string * default: description - * stock: + * passing_score: + * type: integer + * format: int64 + * time_limit: * type: integer * format: int64 - * price: - * type: integer - * format: int64 - * */ /** * @swagger * tags: - * name: Products - * description: The Products managing API + * name: Quizzes + * description: The Quizzes managing API */ /** * @swagger -* /api/products: +* /api/quizzes: * post: * security: * - bearerAuth: [] -* tags: [Products] +* tags: [Quizzes] * summary: Add new item * description: Add new item * requestBody: @@ -72,14 +68,14 @@ router.use(checkCrudPermissions('products')); * data: * description: Data of the updated item * type: object -* $ref: "#/components/schemas/Products" +* $ref: "#/components/schemas/Quizzes" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: -* $ref: "#/components/schemas/Products" +* $ref: "#/components/schemas/Quizzes" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -90,7 +86,7 @@ router.use(checkCrudPermissions('products')); router.post('/', wrapAsync(async (req, res) => { const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await ProductsService.create(req.body.data, req.currentUser, true, link.host); + await QuizzesService.create(req.body.data, req.currentUser, true, link.host); const payload = true; res.status(200).send(payload); })); @@ -101,7 +97,7 @@ router.post('/', wrapAsync(async (req, res) => { * post: * security: * - bearerAuth: [] - * tags: [Products] + * tags: [Quizzes] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -114,14 +110,14 @@ router.post('/', wrapAsync(async (req, res) => { * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -133,18 +129,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 ProductsService.bulkImport(req, res, true, link.host); + await QuizzesService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/products/{id}: + * /api/quizzes/{id}: * put: * security: * - bearerAuth: [] - * tags: [Products] + * tags: [Quizzes] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -167,7 +163,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * required: * - id * responses: @@ -176,7 +172,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 400: * description: Invalid ID supplied * 401: @@ -187,18 +183,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => { * description: Some server error */ router.put('/:id', wrapAsync(async (req, res) => { - await ProductsService.update(req.body.data, req.body.id, req.currentUser); + await QuizzesService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/products/{id}: + * /api/quizzes/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Products] + * tags: [Quizzes] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -214,7 +210,7 @@ router.put('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 400: * description: Invalid ID supplied * 401: @@ -225,18 +221,18 @@ router.put('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.delete('/:id', wrapAsync(async (req, res) => { - await ProductsService.remove(req.params.id, req.currentUser); + await QuizzesService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/products/deleteByIds: + * /api/quizzes/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Products] + * tags: [Quizzes] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -254,7 +250,7 @@ router.delete('/:id', wrapAsync(async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -263,29 +259,29 @@ router.delete('/:id', wrapAsync(async (req, res) => { * description: Some server error */ router.post('/deleteByIds', wrapAsync(async (req, res) => { - await ProductsService.deleteByIds(req.body.data, req.currentUser); + await QuizzesService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); })); /** * @swagger - * /api/products: + * /api/quizzes: * get: * security: * - bearerAuth: [] - * tags: [Products] - * summary: Get all products - * description: Get all products + * tags: [Quizzes] + * summary: Get all quizzes + * description: Get all quizzes * responses: * 200: - * description: Products list successfully received + * description: Quizzes list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -297,13 +293,13 @@ router.get('/', wrapAsync(async (req, res) => { const filetype = req.query.filetype const currentUser = req.currentUser; - const payload = await ProductsDBApi.findAll( + const payload = await QuizzesDBApi.findAll( req.query, { currentUser } ); if (filetype && filetype === 'csv') { - const fields = ['id','sku','name','description', - 'stock', - 'price', + const fields = ['id','title','description', + 'passing_score','time_limit', + ]; const opts = { fields }; @@ -323,22 +319,22 @@ router.get('/', wrapAsync(async (req, res) => { /** * @swagger - * /api/products/count: + * /api/quizzes/count: * get: * security: * - bearerAuth: [] - * tags: [Products] - * summary: Count all products - * description: Count all products + * tags: [Quizzes] + * summary: Count all quizzes + * description: Count all quizzes * responses: * 200: - * description: Products count successfully received + * description: Quizzes count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -349,7 +345,7 @@ router.get('/', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await ProductsDBApi.findAll( + const payload = await QuizzesDBApi.findAll( req.query, null, { countOnly: true, currentUser } @@ -360,22 +356,22 @@ router.get('/count', wrapAsync(async (req, res) => { /** * @swagger - * /api/products/autocomplete: + * /api/quizzes/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Products] - * summary: Find all products that match search criteria - * description: Find all products that match search criteria + * tags: [Quizzes] + * summary: Find all quizzes that match search criteria + * description: Find all quizzes that match search criteria * responses: * 200: - * description: Products list successfully received + * description: Quizzes list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -385,7 +381,7 @@ router.get('/count', wrapAsync(async (req, res) => { */ router.get('/autocomplete', async (req, res) => { - const payload = await ProductsDBApi.findAllAutocomplete( + const payload = await QuizzesDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -397,11 +393,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/products/{id}: + * /api/quizzes/{id}: * get: * security: * - bearerAuth: [] - * tags: [Products] + * tags: [Quizzes] * summary: Get selected item * description: Get selected item * parameters: @@ -417,7 +413,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Products" + * $ref: "#/components/schemas/Quizzes" * 400: * description: Invalid ID supplied * 401: @@ -428,7 +424,7 @@ router.get('/autocomplete', async (req, res) => { * description: Some server error */ router.get('/:id', wrapAsync(async (req, res) => { - const payload = await ProductsDBApi.findBy( + const payload = await QuizzesDBApi.findBy( { id: req.params.id }, ); diff --git a/backend/src/services/announcements.js b/backend/src/services/announcements.js new file mode 100644 index 0000000..50a4f44 --- /dev/null +++ b/backend/src/services/announcements.js @@ -0,0 +1,138 @@ +const db = require('../db/models'); +const AnnouncementsDBApi = require('../db/api/announcements'); +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 AnnouncementsService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await AnnouncementsDBApi.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 AnnouncementsDBApi.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 announcements = await AnnouncementsDBApi.findBy( + {id}, + {transaction}, + ); + + if (!announcements) { + throw new ValidationError( + 'announcementsNotFound', + ); + } + + const updatedAnnouncements = await AnnouncementsDBApi.update( + id, + data, + { + currentUser, + transaction, + }, + ); + + await transaction.commit(); + return updatedAnnouncements; + + } catch (error) { + await transaction.rollback(); + throw error; + } + }; + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await AnnouncementsDBApi.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 AnnouncementsDBApi.remove( + id, + { + currentUser, + transaction, + }, + ); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + +}; + + diff --git a/backend/src/services/order_items.js b/backend/src/services/certificates.js similarity index 83% rename from backend/src/services/order_items.js rename to backend/src/services/certificates.js index 9532731..fa97164 100644 --- a/backend/src/services/order_items.js +++ b/backend/src/services/certificates.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const Order_itemsDBApi = require('../db/api/order_items'); +const CertificatesDBApi = require('../db/api/certificates'); 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 Order_itemsService { +module.exports = class CertificatesService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await Order_itemsDBApi.create( + await CertificatesDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class Order_itemsService { .on('error', (error) => reject(error)); }) - await Order_itemsDBApi.bulkImport(results, { + await CertificatesDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class Order_itemsService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let order_items = await Order_itemsDBApi.findBy( + let certificates = await CertificatesDBApi.findBy( {id}, {transaction}, ); - if (!order_items) { + if (!certificates) { throw new ValidationError( - 'order_itemsNotFound', + 'certificatesNotFound', ); } - const updatedOrder_items = await Order_itemsDBApi.update( + const updatedCertificates = await CertificatesDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class Order_itemsService { ); await transaction.commit(); - return updatedOrder_items; + return updatedCertificates; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class Order_itemsService { const transaction = await db.sequelize.transaction(); try { - await Order_itemsDBApi.deleteByIds(ids, { + await CertificatesDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class Order_itemsService { const transaction = await db.sequelize.transaction(); try { - await Order_itemsDBApi.remove( + await CertificatesDBApi.remove( id, { currentUser, diff --git a/backend/src/services/course_categories.js b/backend/src/services/course_categories.js new file mode 100644 index 0000000..038c7a6 --- /dev/null +++ b/backend/src/services/course_categories.js @@ -0,0 +1,138 @@ +const db = require('../db/models'); +const Course_categoriesDBApi = require('../db/api/course_categories'); +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 Course_categoriesService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await Course_categoriesDBApi.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 Course_categoriesDBApi.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 course_categories = await Course_categoriesDBApi.findBy( + {id}, + {transaction}, + ); + + if (!course_categories) { + throw new ValidationError( + 'course_categoriesNotFound', + ); + } + + const updatedCourse_categories = await Course_categoriesDBApi.update( + id, + data, + { + currentUser, + transaction, + }, + ); + + await transaction.commit(); + return updatedCourse_categories; + + } catch (error) { + await transaction.rollback(); + throw error; + } + }; + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await Course_categoriesDBApi.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 Course_categoriesDBApi.remove( + id, + { + currentUser, + transaction, + }, + ); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + +}; + + diff --git a/backend/src/services/orders.js b/backend/src/services/courses.js similarity index 85% rename from backend/src/services/orders.js rename to backend/src/services/courses.js index 2cac58d..a63b30c 100644 --- a/backend/src/services/orders.js +++ b/backend/src/services/courses.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const OrdersDBApi = require('../db/api/orders'); +const CoursesDBApi = require('../db/api/courses'); 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 OrdersService { +module.exports = class CoursesService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await OrdersDBApi.create( + await CoursesDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class OrdersService { .on('error', (error) => reject(error)); }) - await OrdersDBApi.bulkImport(results, { + await CoursesDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class OrdersService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let orders = await OrdersDBApi.findBy( + let courses = await CoursesDBApi.findBy( {id}, {transaction}, ); - if (!orders) { + if (!courses) { throw new ValidationError( - 'ordersNotFound', + 'coursesNotFound', ); } - const updatedOrders = await OrdersDBApi.update( + const updatedCourses = await CoursesDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class OrdersService { ); await transaction.commit(); - return updatedOrders; + return updatedCourses; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class OrdersService { const transaction = await db.sequelize.transaction(); try { - await OrdersDBApi.deleteByIds(ids, { + await CoursesDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class OrdersService { const transaction = await db.sequelize.transaction(); try { - await OrdersDBApi.remove( + await CoursesDBApi.remove( id, { currentUser, diff --git a/backend/src/services/categories.js b/backend/src/services/enrollments.js similarity index 84% rename from backend/src/services/categories.js rename to backend/src/services/enrollments.js index 90fbeae..7dde01e 100644 --- a/backend/src/services/categories.js +++ b/backend/src/services/enrollments.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const CategoriesDBApi = require('../db/api/categories'); +const EnrollmentsDBApi = require('../db/api/enrollments'); 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 CategoriesService { +module.exports = class EnrollmentsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await CategoriesDBApi.create( + await EnrollmentsDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class CategoriesService { .on('error', (error) => reject(error)); }) - await CategoriesDBApi.bulkImport(results, { + await EnrollmentsDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class CategoriesService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let categories = await CategoriesDBApi.findBy( + let enrollments = await EnrollmentsDBApi.findBy( {id}, {transaction}, ); - if (!categories) { + if (!enrollments) { throw new ValidationError( - 'categoriesNotFound', + 'enrollmentsNotFound', ); } - const updatedCategories = await CategoriesDBApi.update( + const updatedEnrollments = await EnrollmentsDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class CategoriesService { ); await transaction.commit(); - return updatedCategories; + return updatedEnrollments; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class CategoriesService { const transaction = await db.sequelize.transaction(); try { - await CategoriesDBApi.deleteByIds(ids, { + await EnrollmentsDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class CategoriesService { const transaction = await db.sequelize.transaction(); try { - await CategoriesDBApi.remove( + await EnrollmentsDBApi.remove( id, { currentUser, diff --git a/backend/src/services/products.js b/backend/src/services/lessons.js similarity index 85% rename from backend/src/services/products.js rename to backend/src/services/lessons.js index 458d58f..0740b3a 100644 --- a/backend/src/services/products.js +++ b/backend/src/services/lessons.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const ProductsDBApi = require('../db/api/products'); +const LessonsDBApi = require('../db/api/lessons'); 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 ProductsService { +module.exports = class LessonsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await ProductsDBApi.create( + await LessonsDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class ProductsService { .on('error', (error) => reject(error)); }) - await ProductsDBApi.bulkImport(results, { + await LessonsDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class ProductsService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let products = await ProductsDBApi.findBy( + let lessons = await LessonsDBApi.findBy( {id}, {transaction}, ); - if (!products) { + if (!lessons) { throw new ValidationError( - 'productsNotFound', + 'lessonsNotFound', ); } - const updatedProducts = await ProductsDBApi.update( + const updatedLessons = await LessonsDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class ProductsService { ); await transaction.commit(); - return updatedProducts; + return updatedLessons; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class ProductsService { const transaction = await db.sequelize.transaction(); try { - await ProductsDBApi.deleteByIds(ids, { + await LessonsDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class ProductsService { const transaction = await db.sequelize.transaction(); try { - await ProductsDBApi.remove( + await LessonsDBApi.remove( id, { currentUser, diff --git a/backend/src/services/notifications/list.js b/backend/src/services/notifications/list.js index 28b32ad..9df74f5 100644 --- a/backend/src/services/notifications/list.js +++ b/backend/src/services/notifications/list.js @@ -1,6 +1,6 @@ const errors = { app: { - title: 'Store Operations Manager', + title: 'CourseFlow LMS', }, auth: { diff --git a/backend/src/services/payments.js b/backend/src/services/progress.js similarity index 85% rename from backend/src/services/payments.js rename to backend/src/services/progress.js index ef655fe..7b3e7bf 100644 --- a/backend/src/services/payments.js +++ b/backend/src/services/progress.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const PaymentsDBApi = require('../db/api/payments'); +const ProgressDBApi = require('../db/api/progress'); 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 PaymentsService { +module.exports = class ProgressService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await PaymentsDBApi.create( + await ProgressDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class PaymentsService { .on('error', (error) => reject(error)); }) - await PaymentsDBApi.bulkImport(results, { + await ProgressDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class PaymentsService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let payments = await PaymentsDBApi.findBy( + let progress = await ProgressDBApi.findBy( {id}, {transaction}, ); - if (!payments) { + if (!progress) { throw new ValidationError( - 'paymentsNotFound', + 'progressNotFound', ); } - const updatedPayments = await PaymentsDBApi.update( + const updatedProgress = await ProgressDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class PaymentsService { ); await transaction.commit(); - return updatedPayments; + return updatedProgress; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class PaymentsService { const transaction = await db.sequelize.transaction(); try { - await PaymentsDBApi.deleteByIds(ids, { + await ProgressDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class PaymentsService { const transaction = await db.sequelize.transaction(); try { - await PaymentsDBApi.remove( + await ProgressDBApi.remove( id, { currentUser, diff --git a/backend/src/services/quiz_questions.js b/backend/src/services/quiz_questions.js new file mode 100644 index 0000000..692b652 --- /dev/null +++ b/backend/src/services/quiz_questions.js @@ -0,0 +1,138 @@ +const db = require('../db/models'); +const Quiz_questionsDBApi = require('../db/api/quiz_questions'); +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 Quiz_questionsService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await Quiz_questionsDBApi.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 Quiz_questionsDBApi.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 quiz_questions = await Quiz_questionsDBApi.findBy( + {id}, + {transaction}, + ); + + if (!quiz_questions) { + throw new ValidationError( + 'quiz_questionsNotFound', + ); + } + + const updatedQuiz_questions = await Quiz_questionsDBApi.update( + id, + data, + { + currentUser, + transaction, + }, + ); + + await transaction.commit(); + return updatedQuiz_questions; + + } catch (error) { + await transaction.rollback(); + throw error; + } + }; + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await Quiz_questionsDBApi.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 Quiz_questionsDBApi.remove( + id, + { + currentUser, + transaction, + }, + ); + + await transaction.commit(); + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + +}; + + diff --git a/backend/src/services/customers.js b/backend/src/services/quizzes.js similarity index 85% rename from backend/src/services/customers.js rename to backend/src/services/quizzes.js index 4232e6d..cd99dab 100644 --- a/backend/src/services/customers.js +++ b/backend/src/services/quizzes.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const CustomersDBApi = require('../db/api/customers'); +const QuizzesDBApi = require('../db/api/quizzes'); 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 CustomersService { +module.exports = class QuizzesService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await CustomersDBApi.create( + await QuizzesDBApi.create( data, { currentUser, @@ -51,7 +51,7 @@ module.exports = class CustomersService { .on('error', (error) => reject(error)); }) - await CustomersDBApi.bulkImport(results, { + await QuizzesDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -68,18 +68,18 @@ module.exports = class CustomersService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let customers = await CustomersDBApi.findBy( + let quizzes = await QuizzesDBApi.findBy( {id}, {transaction}, ); - if (!customers) { + if (!quizzes) { throw new ValidationError( - 'customersNotFound', + 'quizzesNotFound', ); } - const updatedCustomers = await CustomersDBApi.update( + const updatedQuizzes = await QuizzesDBApi.update( id, data, { @@ -89,7 +89,7 @@ module.exports = class CustomersService { ); await transaction.commit(); - return updatedCustomers; + return updatedQuizzes; } catch (error) { await transaction.rollback(); @@ -101,7 +101,7 @@ module.exports = class CustomersService { const transaction = await db.sequelize.transaction(); try { - await CustomersDBApi.deleteByIds(ids, { + await QuizzesDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -117,7 +117,7 @@ module.exports = class CustomersService { const transaction = await db.sequelize.transaction(); try { - await CustomersDBApi.remove( + await QuizzesDBApi.remove( id, { currentUser, diff --git a/backend/src/services/search.js b/backend/src/services/search.js index 318018c..d1bf428 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -66,12 +66,12 @@ module.exports = class SearchService { - "products": [ - - "sku", + "course_categories": [ "name", + "slug", + "description", ], @@ -81,12 +81,16 @@ module.exports = class SearchService { - "categories": [ + "courses": [ - "name", + "title", + + "short_description", "description", + "language", + ], @@ -94,19 +98,48 @@ module.exports = class SearchService { - "customers": [ + "lessons": [ - "name", + "title", - "email", + "content", - "phone", + ], + + + + + + + "enrollments": [ - "address", + "enrollment_code", + + ], + + + + + + + "progress": [ + + "summary", "notes", - "tax_number", + ], + + + + + + + "quizzes": [ + + "title", + + "description", ], @@ -115,11 +148,13 @@ module.exports = class SearchService { - "orders": [ + "quiz_questions": [ - "order_number", + "question", - "shipping_address", + "choices", + + "answer", ], @@ -128,9 +163,11 @@ module.exports = class SearchService { - "order_items": [ + "announcements": [ - "name", + "title", + + "content", ], @@ -139,22 +176,9 @@ module.exports = class SearchService { - "payments": [ + "certificates": [ - "reference", - - ], - - - - - - - "shipments": [ - - "carrier", - - "tracking_number", + "serial", ], @@ -171,11 +195,59 @@ module.exports = class SearchService { - "products": [ + + + + + "courses": [ "price", - "stock", + "duration", + + ], + + + + + + "lessons": [ + + "order", + + "duration", + + ], + + + + + + "enrollments": [ + + "progress_percent", + + ], + + + + + + "progress": [ + + "percent", + + ], + + + + + + "quizzes": [ + + "passing_score", + + "time_limit", ], @@ -191,40 +263,6 @@ module.exports = class SearchService { - "orders": [ - - "total", - - ], - - - - - - "order_items": [ - - "quantity", - - "unit_price", - - "total_price", - - ], - - - - - - "payments": [ - - "amount", - - ], - - - - - }; diff --git a/backend/src/services/shipments.js b/backend/src/services/shipments.js deleted file mode 100644 index 48ba951..0000000 --- a/backend/src/services/shipments.js +++ /dev/null @@ -1,138 +0,0 @@ -const db = require('../db/models'); -const ShipmentsDBApi = require('../db/api/shipments'); -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 ShipmentsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await ShipmentsDBApi.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 ShipmentsDBApi.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 shipments = await ShipmentsDBApi.findBy( - {id}, - {transaction}, - ); - - if (!shipments) { - throw new ValidationError( - 'shipmentsNotFound', - ); - } - - const updatedShipments = await ShipmentsDBApi.update( - id, - data, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedShipments; - - } catch (error) { - await transaction.rollback(); - throw error; - } - }; - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await ShipmentsDBApi.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 ShipmentsDBApi.remove( - id, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - -}; - - diff --git a/backend/watcher.js b/backend/watcher.js index 3796f8d..9e5e5bc 100644 --- a/backend/watcher.js +++ b/backend/watcher.js @@ -2,7 +2,7 @@ const chokidar = require('chokidar'); const { exec } = require('child_process'); const nodemon = require('nodemon'); -const nodeEnv = 'dev_stage'; +const nodeEnv = process.env.NODE_ENV || 'dev_stage'; const childEnv = { ...process.env, NODE_ENV: nodeEnv }; const migrationsWatcher = chokidar.watch('./src/db/migrations', { diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c803e34..2a9d6df 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_store_operations_manager + - POSTGRES_DB=db_courseflow_lms ports: - "5432:5432" logging: diff --git a/frontend/README.md b/frontend/README.md index 5235cd6..d88f7ae 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,4 +1,4 @@ -# Store Operations Manager +# CourseFlow LMS ## This project was generated by Flatlogic Platform. ## Install diff --git a/frontend/src/components/Shipments/CardShipments.tsx b/frontend/src/components/Announcements/CardAnnouncements.tsx similarity index 78% rename from frontend/src/components/Shipments/CardShipments.tsx rename to frontend/src/components/Announcements/CardAnnouncements.tsx index cb42302..279465b 100644 --- a/frontend/src/components/Shipments/CardShipments.tsx +++ b/frontend/src/components/Announcements/CardAnnouncements.tsx @@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - shipments: any[]; + announcements: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardShipments = ({ - shipments, +const CardAnnouncements = ({ + announcements, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardShipments = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_SHIPMENTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ANNOUNCEMENTS') return ( @@ -47,7 +47,7 @@ const CardShipments = ({ 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 && shipments.map((item, index) => ( + {!loading && announcements.map((item, index) => (
  • - - {item.tracking_number} + + {item.title} @@ -66,8 +66,8 @@ const CardShipments = ({ -
    Order
    +
    Course
    - { dataFormatter.ordersOneListFormatter(item.order) } + { dataFormatter.coursesOneListFormatter(item.course) }
  • @@ -90,10 +90,10 @@ const CardShipments = ({
    -
    Carrier
    +
    Title
    - { item.carrier } + { item.title }
    @@ -102,10 +102,10 @@ const CardShipments = ({
    -
    TrackingNumber
    +
    Content
    - { item.tracking_number } + { item.content }
    @@ -114,10 +114,10 @@ const CardShipments = ({
    -
    ShippedAt
    +
    PublishedAt
    - { dataFormatter.dateTimeFormatter(item.shipped_at) } + { dataFormatter.dateTimeFormatter(item.published_at) }
    @@ -126,22 +126,10 @@ const CardShipments = ({
    -
    DeliveredAt
    +
    Active
    - { dataFormatter.dateTimeFormatter(item.delivered_at) } -
    -
    -
    - - - - -
    -
    Status
    -
    -
    - { item.status } + { dataFormatter.booleanFormatter(item.is_active) }
    @@ -151,7 +139,7 @@ const CardShipments = ({ ))} - {!loading && shipments.length === 0 && ( + {!loading && announcements.length === 0 && (

    No data to display

    @@ -168,4 +156,4 @@ const CardShipments = ({ ); }; -export default CardShipments; +export default CardAnnouncements; diff --git a/frontend/src/components/Order_items/ListOrder_items.tsx b/frontend/src/components/Announcements/ListAnnouncements.tsx similarity index 78% rename from frontend/src/components/Order_items/ListOrder_items.tsx rename to frontend/src/components/Announcements/ListAnnouncements.tsx index 7b50c9d..844024f 100644 --- a/frontend/src/components/Order_items/ListOrder_items.tsx +++ b/frontend/src/components/Announcements/ListAnnouncements.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - order_items: any[]; + announcements: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListAnnouncements = ({ announcements, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ORDER_ITEMS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ANNOUNCEMENTS') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages <>
    {loading && } - {!loading && order_items.map((item) => ( + {!loading && announcements.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -48,48 +48,40 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
    -

    Name

    -

    { item.name }

    +

    Course

    +

    { dataFormatter.coursesOneListFormatter(item.course) }

    -

    Order

    -

    { dataFormatter.ordersOneListFormatter(item.order) }

    +

    Title

    +

    { item.title }

    -

    Product

    -

    { dataFormatter.productsOneListFormatter(item.product) }

    +

    Content

    +

    { item.content }

    -

    Quantity

    -

    { item.quantity }

    +

    PublishedAt

    +

    { dataFormatter.dateTimeFormatter(item.published_at) }

    -

    UnitPrice

    -

    { item.unit_price }

    -
    - - - - -
    -

    TotalPrice

    -

    { item.total_price }

    +

    Active

    +

    { dataFormatter.booleanFormatter(item.is_active) }

    @@ -98,8 +90,8 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
    ))} - {!loading && order_items.length === 0 && ( + {!loading && announcements.length === 0 && (

    No data to display

    @@ -125,4 +117,4 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages ) }; -export default ListOrder_items \ No newline at end of file +export default ListAnnouncements \ No newline at end of file diff --git a/frontend/src/components/Announcements/TableAnnouncements.tsx b/frontend/src/components/Announcements/TableAnnouncements.tsx new file mode 100644 index 0000000..f740bdf --- /dev/null +++ b/frontend/src/components/Announcements/TableAnnouncements.tsx @@ -0,0 +1,476 @@ +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/announcements/announcementsSlice' +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 "./configureAnnouncementsCols"; +import _ from 'lodash'; +import dataFormatter from '../../helpers/dataFormatter' +import {dataGridStyles} from "../../styles"; + + +import ListAnnouncements from './ListAnnouncements'; + + +const perPage = 10 + +const TableSampleAnnouncements = ({ 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 { announcements, loading, count, notify: announcementsNotify, refetch } = useAppSelector((state) => state.announcements) + 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 (announcementsNotify.showNotification) { + notify(announcementsNotify.typeNotification, announcementsNotify.textNotification); + } + }, [announcementsNotify.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, + `announcements`, + 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={announcements ?? []} + 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?

    +
    + + + {announcements && Array.isArray(announcements) && !showGrid && ( + + )} + + + + {showGrid && dataGrid} + + + {selectedRows.length > 0 && + createPortal( + onDeleteRows(selectedRows)} + />, + document.getElementById('delete-rows-button'), + )} + + + ) +} + +export default TableSampleAnnouncements diff --git a/frontend/src/components/Shipments/configureShipmentsCols.tsx b/frontend/src/components/Announcements/configureAnnouncementsCols.tsx similarity index 75% rename from frontend/src/components/Shipments/configureShipmentsCols.tsx rename to frontend/src/components/Announcements/configureAnnouncementsCols.tsx index 8f9b39f..8a62ed4 100644 --- a/frontend/src/components/Shipments/configureShipmentsCols.tsx +++ b/frontend/src/components/Announcements/configureAnnouncementsCols.tsx @@ -37,13 +37,13 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_SHIPMENTS') + const hasUpdatePermission = hasPermission(user, 'UPDATE_ANNOUNCEMENTS') return [ { - field: 'order', - headerName: 'Order', + field: 'course', + headerName: 'Course', flex: 1, minWidth: 120, filterable: false, @@ -57,15 +57,15 @@ export const loadColumns = async ( type: 'singleSelect', getOptionValue: (value: any) => value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('orders'), + valueOptions: await callOptionsApi('courses'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, { - field: 'carrier', - headerName: 'Carrier', + field: 'title', + headerName: 'Title', flex: 1, minWidth: 120, filterable: false, @@ -79,8 +79,8 @@ export const loadColumns = async ( }, { - field: 'tracking_number', - headerName: 'TrackingNumber', + field: 'content', + headerName: 'Content', flex: 1, minWidth: 120, filterable: false, @@ -94,8 +94,8 @@ export const loadColumns = async ( }, { - field: 'shipped_at', - headerName: 'ShippedAt', + field: 'published_at', + headerName: 'PublishedAt', flex: 1, minWidth: 120, filterable: false, @@ -107,13 +107,13 @@ export const loadColumns = async ( type: 'dateTime', valueGetter: (params: GridValueGetterParams) => - new Date(params.row.shipped_at), + new Date(params.row.published_at), }, { - field: 'delivered_at', - headerName: 'DeliveredAt', + field: 'is_active', + headerName: 'Active', flex: 1, minWidth: 120, filterable: false, @@ -123,27 +123,10 @@ export const loadColumns = async ( editable: hasUpdatePermission, - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.delivered_at), + type: 'boolean', }, - { - field: 'status', - headerName: 'Status', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - { field: 'actions', type: 'actions', @@ -157,8 +140,8 @@ export const loadColumns = async (
    - Store Operations Manager + CourseFlow LMS
    diff --git a/frontend/src/components/Certificates/CardCertificates.tsx b/frontend/src/components/Certificates/CardCertificates.tsx new file mode 100644 index 0000000..fe66c4c --- /dev/null +++ b/frontend/src/components/Certificates/CardCertificates.tsx @@ -0,0 +1,166 @@ +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 = { + certificates: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const CardCertificates = ({ + certificates, + 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_CERTIFICATES') + + + return ( +
    + {loading && } +
      + {!loading && certificates.map((item, index) => ( +
    • + +
      + + + {item.serial} + + + +
      + +
      +
      +
      + + +
      +
      Serial
      +
      +
      + { item.serial } +
      +
      +
      + + + + +
      +
      Student
      +
      +
      + { dataFormatter.usersOneListFormatter(item.student) } +
      +
      +
      + + + + +
      +
      Course
      +
      +
      + { dataFormatter.coursesOneListFormatter(item.course) } +
      +
      +
      + + + + +
      +
      IssuedAt
      +
      +
      + { dataFormatter.dateTimeFormatter(item.issued_at) } +
      +
      +
      + + + + +
      +
      File
      +
      +
      + {dataFormatter.filesFormatter(item.file).map(link => ( + + ))} +
      +
      +
      + + + +
      +
    • + ))} + {!loading && certificates.length === 0 && ( +
      +

      No data to display

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

    Serial

    +

    { item.serial }

    +
    + + + + +
    +

    Student

    +

    { dataFormatter.usersOneListFormatter(item.student) }

    +
    + + + + +
    +

    Course

    +

    { dataFormatter.coursesOneListFormatter(item.course) }

    +
    + + + + +
    +

    IssuedAt

    +

    { dataFormatter.dateTimeFormatter(item.issued_at) }

    +
    + + + + +
    +

    File

    + {dataFormatter.filesFormatter(item.file).map(link => ( + + ))} +
    + + + + + +
    +
    +
    + ))} + {!loading && certificates.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ) +}; + +export default ListCertificates \ No newline at end of file diff --git a/frontend/src/components/Order_items/TableOrder_items.tsx b/frontend/src/components/Certificates/TableCertificates.tsx similarity index 96% rename from frontend/src/components/Order_items/TableOrder_items.tsx rename to frontend/src/components/Certificates/TableCertificates.tsx index e28d03a..64b77a6 100644 --- a/frontend/src/components/Order_items/TableOrder_items.tsx +++ b/frontend/src/components/Certificates/TableCertificates.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/order_items/order_itemsSlice' +import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/certificates/certificatesSlice' 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 "./configureOrder_itemsCols"; +import {loadColumns} from "./configureCertificatesCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; @@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles"; const perPage = 10 -const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleCertificates = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -40,7 +40,7 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid }, ]); - const { order_items, loading, count, notify: order_itemsNotify, refetch } = useAppSelector((state) => state.order_items) + const { certificates, loading, count, notify: certificatesNotify, refetch } = useAppSelector((state) => state.certificates) 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 TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid }; useEffect(() => { - if (order_itemsNotify.showNotification) { - notify(order_itemsNotify.typeNotification, order_itemsNotify.textNotification); + if (certificatesNotify.showNotification) { + notify(certificatesNotify.typeNotification, certificatesNotify.textNotification); } - }, [order_itemsNotify.showNotification]); + }, [certificatesNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -177,7 +177,7 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid loadColumns( handleDeleteModalAction, - `order_items`, + `certificates`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -215,7 +215,7 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={order_items ?? []} + rows={certificates ?? []} columns={columns} initialState={{ pagination: { @@ -460,4 +460,4 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid ) } -export default TableSampleOrder_items +export default TableSampleCertificates diff --git a/frontend/src/components/Certificates/configureCertificatesCols.tsx b/frontend/src/components/Certificates/configureCertificatesCols.tsx new file mode 100644 index 0000000..4db3670 --- /dev/null +++ b/frontend/src/components/Certificates/configureCertificatesCols.tsx @@ -0,0 +1,171 @@ +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_CERTIFICATES') + + return [ + + { + field: 'serial', + headerName: 'Serial', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'student', + headerName: 'Student', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('users'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + + }, + + { + field: '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: 'issued_at', + headerName: 'IssuedAt', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.issued_at), + + }, + + { + field: 'file', + headerName: 'File', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: false, + sortable: false, + renderCell: (params: GridValueGetterParams) => ( + <> + {dataFormatter.filesFormatter(params.row.file).map(link => ( + + ))} + + ), + + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + + return [ +
    + +
    , + ] + }, + }, + ]; +}; diff --git a/frontend/src/components/Categories/CardCategories.tsx b/frontend/src/components/Course_categories/CardCourse_categories.tsx similarity index 86% rename from frontend/src/components/Categories/CardCategories.tsx rename to frontend/src/components/Course_categories/CardCourse_categories.tsx index 1f3c6b1..1136fc2 100644 --- a/frontend/src/components/Categories/CardCategories.tsx +++ b/frontend/src/components/Course_categories/CardCourse_categories.tsx @@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - categories: any[]; + course_categories: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardCategories = ({ - categories, +const CardCourse_categories = ({ + course_categories, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardCategories = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CATEGORIES') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSE_CATEGORIES') return ( @@ -47,7 +47,7 @@ const CardCategories = ({ 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 && categories.map((item, index) => ( + {!loading && course_categories.map((item, index) => (
  • - + {item.name} @@ -66,8 +66,8 @@ const CardCategories = ({ +
    Slug
    +
    +
    + { item.slug } +
    +
    +
  • + + + +
    Description
    @@ -100,22 +112,10 @@ const CardCategories = ({ - -
    -
    ParentCategory
    -
    -
    - { dataFormatter.categoriesOneListFormatter(item.parent) } -
    -
    -
    - - - ))} - {!loading && categories.length === 0 && ( + {!loading && course_categories.length === 0 && (

    No data to display

    @@ -132,4 +132,4 @@ const CardCategories = ({ ); }; -export default CardCategories; +export default CardCourse_categories; diff --git a/frontend/src/components/Categories/ListCategories.tsx b/frontend/src/components/Course_categories/ListCourse_categories.tsx similarity index 84% rename from frontend/src/components/Categories/ListCategories.tsx rename to frontend/src/components/Course_categories/ListCourse_categories.tsx index 41cbbc4..7d42372 100644 --- a/frontend/src/components/Categories/ListCategories.tsx +++ b/frontend/src/components/Course_categories/ListCourse_categories.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - categories: any[]; + course_categories: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListCategories = ({ categories, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListCourse_categories = ({ course_categories, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CATEGORIES') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSE_CATEGORIES') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages, <>
    {loading && } - {!loading && categories.map((item) => ( + {!loading && course_categories.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -56,16 +56,16 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
    -

    Description

    -

    { item.description }

    +

    Slug

    +

    { item.slug }

    -

    ParentCategory

    -

    { dataFormatter.categoriesOneListFormatter(item.parent) }

    +

    Description

    +

    { item.description }

    @@ -74,8 +74,8 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
    ))} - {!loading && categories.length === 0 && ( + {!loading && course_categories.length === 0 && (

    No data to display

    @@ -101,4 +101,4 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages, ) }; -export default ListCategories \ No newline at end of file +export default ListCourse_categories \ No newline at end of file diff --git a/frontend/src/components/Course_categories/TableCourse_categories.tsx b/frontend/src/components/Course_categories/TableCourse_categories.tsx new file mode 100644 index 0000000..5717695 --- /dev/null +++ b/frontend/src/components/Course_categories/TableCourse_categories.tsx @@ -0,0 +1,476 @@ +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/course_categories/course_categoriesSlice' +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 "./configureCourse_categoriesCols"; +import _ from 'lodash'; +import dataFormatter from '../../helpers/dataFormatter' +import {dataGridStyles} from "../../styles"; + + +import ListCourse_categories from './ListCourse_categories'; + + +const perPage = 10 + +const TableSampleCourse_categories = ({ 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 { course_categories, loading, count, notify: course_categoriesNotify, refetch } = useAppSelector((state) => state.course_categories) + 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 (course_categoriesNotify.showNotification) { + notify(course_categoriesNotify.typeNotification, course_categoriesNotify.textNotification); + } + }, [course_categoriesNotify.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, + `course_categories`, + 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={course_categories ?? []} + 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?

    +
    + + + {course_categories && Array.isArray(course_categories) && !showGrid && ( + + )} + + + + {showGrid && dataGrid} + + + {selectedRows.length > 0 && + createPortal( + onDeleteRows(selectedRows)} + />, + document.getElementById('delete-rows-button'), + )} + + + ) +} + +export default TableSampleCourse_categories diff --git a/frontend/src/components/Categories/configureCategoriesCols.tsx b/frontend/src/components/Course_categories/configureCourse_categoriesCols.tsx similarity index 80% rename from frontend/src/components/Categories/configureCategoriesCols.tsx rename to frontend/src/components/Course_categories/configureCourse_categoriesCols.tsx index 35f9743..7e1b167 100644 --- a/frontend/src/components/Categories/configureCategoriesCols.tsx +++ b/frontend/src/components/Course_categories/configureCourse_categoriesCols.tsx @@ -37,7 +37,7 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_CATEGORIES') + const hasUpdatePermission = hasPermission(user, 'UPDATE_COURSE_CATEGORIES') return [ @@ -54,6 +54,21 @@ export const loadColumns = async ( editable: hasUpdatePermission, + }, + + { + field: 'slug', + headerName: 'Slug', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + }, { @@ -71,28 +86,6 @@ export const loadColumns = async ( }, - { - field: 'parent', - headerName: 'ParentCategory', - 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('categories'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - - }, - { field: 'actions', type: 'actions', @@ -106,8 +99,8 @@ export const loadColumns = async ( void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardProducts = ({ - products, +const CardCourses = ({ + courses, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardProducts = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PRODUCTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSES') return ( @@ -47,7 +47,7 @@ const CardProducts = ({ 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 && products.map((item, index) => ( + {!loading && courses.map((item, index) => (
  • -

    {item.name}

    +

    {item.title}

    @@ -75,8 +75,8 @@ const CardProducts = ({ -
    SKU
    +
    Title
    - { item.sku } + { item.title }
  • @@ -99,10 +99,10 @@ const CardProducts = ({
    -
    Name
    +
    ShortDescription
    - { item.name } + { item.short_description }
    @@ -122,6 +122,54 @@ const CardProducts = ({ +
    +
    Instructor
    +
    +
    + { dataFormatter.usersOneListFormatter(item.instructor) } +
    +
    +
    + + + + +
    +
    Category
    +
    +
    + { dataFormatter.course_categoriesOneListFormatter(item.category) } +
    +
    +
    + + + + +
    +
    Level
    +
    +
    + { item.level } +
    +
    +
    + + + + +
    +
    Language
    +
    +
    + { item.language } +
    +
    +
    + + + +
    Price
    @@ -135,10 +183,10 @@ const CardProducts = ({
    -
    Stock
    +
    Published
    - { item.stock } + { dataFormatter.booleanFormatter(item.published) }
    @@ -147,12 +195,24 @@ const CardProducts = ({
    -
    Images
    +
    PublishedAt
    +
    +
    + { dataFormatter.dateTimeFormatter(item.published_at) } +
    +
    +
    + + + + +
    +
    Thumbnail
    @@ -163,22 +223,10 @@ const CardProducts = ({
    -
    Category
    +
    Duration(minutes)
    - { dataFormatter.categoriesOneListFormatter(item.category) } -
    -
    -
    - - - - -
    -
    Status
    -
    -
    - { item.status } + { item.duration }
    @@ -188,7 +236,7 @@ const CardProducts = ({ ))} - {!loading && products.length === 0 && ( + {!loading && courses.length === 0 && (

    No data to display

    @@ -205,4 +253,4 @@ const CardProducts = ({ ); }; -export default CardProducts; +export default CardCourses; diff --git a/frontend/src/components/Products/ListProducts.tsx b/frontend/src/components/Courses/ListCourses.tsx similarity index 70% rename from frontend/src/components/Products/ListProducts.tsx rename to frontend/src/components/Courses/ListCourses.tsx index a1dfd26..d1ae0a6 100644 --- a/frontend/src/components/Products/ListProducts.tsx +++ b/frontend/src/components/Courses/ListCourses.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - products: any[]; + courses: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListCourses = ({ courses, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PRODUCTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSES') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,20 +34,20 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa <>
    {loading && } - {!loading && products.map((item) => ( + {!loading && courses.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -55,16 +55,16 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
    -

    SKU

    -

    { item.sku }

    +

    Title

    +

    { item.title }

    -

    Name

    -

    { item.name }

    +

    ShortDescription

    +

    { item.short_description }

    @@ -78,6 +78,38 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa +
    +

    Instructor

    +

    { dataFormatter.usersOneListFormatter(item.instructor) }

    +
    + + + + +
    +

    Category

    +

    { dataFormatter.course_categoriesOneListFormatter(item.category) }

    +
    + + + + +
    +

    Level

    +

    { item.level }

    +
    + + + + +
    +

    Language

    +

    { item.language }

    +
    + + + +

    Price

    { item.price }

    @@ -87,18 +119,26 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
    -

    Stock

    -

    { item.stock }

    +

    Published

    +

    { dataFormatter.booleanFormatter(item.published) }

    -

    Images

    +

    PublishedAt

    +

    { dataFormatter.dateTimeFormatter(item.published_at) }

    +
    + + + + +
    +

    Thumbnail

    @@ -107,16 +147,8 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
    -

    Category

    -

    { dataFormatter.categoriesOneListFormatter(item.category) }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    +

    Duration(minutes)

    +

    { item.duration }

    @@ -125,8 +157,8 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
    ))} - {!loading && products.length === 0 && ( + {!loading && courses.length === 0 && (

    No data to display

    @@ -152,4 +184,4 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa ) }; -export default ListProducts \ No newline at end of file +export default ListCourses \ No newline at end of file diff --git a/frontend/src/components/Categories/TableCategories.tsx b/frontend/src/components/Courses/TableCourses.tsx similarity index 95% rename from frontend/src/components/Categories/TableCategories.tsx rename to frontend/src/components/Courses/TableCourses.tsx index fb2f687..239f421 100644 --- a/frontend/src/components/Categories/TableCategories.tsx +++ b/frontend/src/components/Courses/TableCourses.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/categories/categoriesSlice' +import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/courses/coursesSlice' 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 "./configureCategoriesCols"; +import {loadColumns} from "./configureCoursesCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; -import ListCategories from './ListCategories'; +import CardCourses from './CardCourses'; const perPage = 10 -const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleCourses = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -42,7 +42,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid }, ]); - const { categories, loading, count, notify: categoriesNotify, refetch } = useAppSelector((state) => state.categories) + const { courses, loading, count, notify: coursesNotify, refetch } = useAppSelector((state) => state.courses) 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 TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid }; useEffect(() => { - if (categoriesNotify.showNotification) { - notify(categoriesNotify.typeNotification, categoriesNotify.textNotification); + if (coursesNotify.showNotification) { + notify(coursesNotify.typeNotification, coursesNotify.textNotification); } - }, [categoriesNotify.showNotification]); + }, [coursesNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -179,7 +179,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid loadColumns( handleDeleteModalAction, - `categories`, + `courses`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -217,7 +217,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={categories ?? []} + rows={courses ?? []} columns={columns} initialState={{ pagination: { @@ -442,9 +442,9 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid - {categories && Array.isArray(categories) && !showGrid && ( - 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_COURSES') + + return [ + + { + field: 'title', + headerName: 'Title', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'short_description', + headerName: 'ShortDescription', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'description', + headerName: 'Description', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'instructor', + headerName: 'Instructor', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + sortable: false, + type: 'singleSelect', + getOptionValue: (value: any) => value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('users'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + + }, + + { + field: 'category', + headerName: 'Category', + 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('course_categories'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + + }, + + { + field: 'level', + headerName: 'Level', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'language', + headerName: 'Language', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'price', + headerName: 'Price', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'number', + + }, + + { + field: 'published', + headerName: 'Published', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'boolean', + + }, + + { + field: 'published_at', + headerName: 'PublishedAt', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.published_at), + + }, + + { + field: 'thumbnail', + headerName: 'Thumbnail', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: false, + sortable: false, + renderCell: (params: GridValueGetterParams) => ( + + ), + + }, + + { + field: 'duration', + headerName: 'Duration(minutes)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'number', + + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + + return [ +
    + +
    , + ] + }, + }, + ]; +}; diff --git a/frontend/src/components/Enrollments/CardEnrollments.tsx b/frontend/src/components/Enrollments/CardEnrollments.tsx new file mode 100644 index 0000000..ff28995 --- /dev/null +++ b/frontend/src/components/Enrollments/CardEnrollments.tsx @@ -0,0 +1,190 @@ +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 = { + enrollments: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const CardEnrollments = ({ + enrollments, + 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_ENROLLMENTS') + + + return ( +
    + {loading && } +
      + {!loading && enrollments.map((item, index) => ( +
    • + +
      + + + {item.enrollment_code} + + + +
      + +
      +
      +
      + + +
      +
      EnrollmentCode
      +
      +
      + { item.enrollment_code } +
      +
      +
      + + + + +
      +
      Student
      +
      +
      + { dataFormatter.usersOneListFormatter(item.student) } +
      +
      +
      + + + + +
      +
      Course
      +
      +
      + { dataFormatter.coursesOneListFormatter(item.course) } +
      +
      +
      + + + + +
      +
      EnrolledAt
      +
      +
      + { dataFormatter.dateTimeFormatter(item.enrolled_at) } +
      +
      +
      + + + + +
      +
      Status
      +
      +
      + { item.status } +
      +
      +
      + + + + +
      +
      Progress(%)
      +
      +
      + { item.progress_percent } +
      +
      +
      + + + + +
      +
      CertificateFile
      +
      +
      + {dataFormatter.filesFormatter(item.certificate).map(link => ( + + ))} +
      +
      +
      + + + +
      +
    • + ))} + {!loading && enrollments.length === 0 && ( +
      +

      No data to display

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

    EnrollmentCode

    +

    { item.enrollment_code }

    +
    + + + + +
    +

    Student

    +

    { dataFormatter.usersOneListFormatter(item.student) }

    +
    + + + + +
    +

    Course

    +

    { dataFormatter.coursesOneListFormatter(item.course) }

    +
    + + + + +
    +

    EnrolledAt

    +

    { dataFormatter.dateTimeFormatter(item.enrolled_at) }

    +
    + + + + +
    +

    Status

    +

    { item.status }

    +
    + + + + +
    +

    Progress(%)

    +

    { item.progress_percent }

    +
    + + + + +
    +

    CertificateFile

    + {dataFormatter.filesFormatter(item.certificate).map(link => ( + + ))} +
    + + + + + +
    +
    +
    + ))} + {!loading && enrollments.length === 0 && ( +
    +

    No data to display

    +
    + )} +
    +
    + +
    + + ) +}; + +export default ListEnrollments \ No newline at end of file diff --git a/frontend/src/components/Orders/TableOrders.tsx b/frontend/src/components/Enrollments/TableEnrollments.tsx similarity index 95% rename from frontend/src/components/Orders/TableOrders.tsx rename to frontend/src/components/Enrollments/TableEnrollments.tsx index 3c82851..7d04c9b 100644 --- a/frontend/src/components/Orders/TableOrders.tsx +++ b/frontend/src/components/Enrollments/TableEnrollments.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/orders/ordersSlice' +import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/enrollments/enrollmentsSlice' 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 "./configureOrdersCols"; +import {loadColumns} from "./configureEnrollmentsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; @@ -24,7 +24,7 @@ import axios from 'axios'; const perPage = 10 -const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleEnrollments = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -46,7 +46,7 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) = const [kanbanColumns, setKanbanColumns] = useState | null>(null); const [kanbanFilters, setKanbanFilters] = useState(''); - const { orders, loading, count, notify: ordersNotify, refetch } = useAppSelector((state) => state.orders) + const { enrollments, loading, count, notify: enrollmentsNotify, refetch } = useAppSelector((state) => state.enrollments) 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 TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) = }; useEffect(() => { - if (ordersNotify.showNotification) { - notify(ordersNotify.typeNotification, ordersNotify.textNotification); + if (enrollmentsNotify.showNotification) { + notify(enrollmentsNotify.typeNotification, enrollmentsNotify.textNotification); } - }, [ordersNotify.showNotification]); + }, [enrollmentsNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -98,19 +98,13 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) = setKanbanColumns([ - { id: "Pending", label: "Pending" }, + { id: "pending", label: "pending" }, - { id: "Processing", label: "Processing" }, + { id: "active", label: "active" }, - { id: "Shipped", label: "Shipped" }, + { id: "completed", label: "completed" }, - { id: "Delivered", label: "Delivered" }, - - { id: "Cancelled", label: "Cancelled" }, - - { id: "Returned", label: "Returned" }, - - { id: "Refunded", label: "Refunded" }, + { id: "dropped", label: "dropped" }, ]); @@ -214,7 +208,7 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) = loadColumns( handleDeleteModalAction, - `orders`, + `enrollments`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -252,7 +246,7 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) = sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={orders ?? []} + rows={enrollments ?? []} columns={columns} initialState={{ pagination: { @@ -481,8 +475,8 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) = {!showGrid && kanbanColumns && ( ( - - ), - - }, - - { - field: 'category', - headerName: 'Category', + field: 'student', + headerName: 'Student', flex: 1, minWidth: 120, filterable: false, @@ -155,12 +72,52 @@ export const loadColumns = async ( type: 'singleSelect', getOptionValue: (value: any) => value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('categories'), + valueOptions: await callOptionsApi('users'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, + { + 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', @@ -176,6 +133,48 @@ export const loadColumns = async ( }, + { + field: 'progress_percent', + headerName: 'Progress(%)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'number', + + }, + + { + field: 'certificate', + headerName: 'CertificateFile', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: false, + sortable: false, + renderCell: (params: GridValueGetterParams) => ( + <> + {dataFormatter.filesFormatter(params.row.certificate).map(link => ( + + ))} + + ), + + }, + { field: 'actions', type: 'actions', @@ -189,8 +188,8 @@ export const loadColumns = async ( void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardOrders = ({ - orders, +const CardLessons = ({ + lessons, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardOrders = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ORDERS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LESSONS') return ( @@ -47,7 +47,7 @@ const CardOrders = ({ 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 && orders.map((item, index) => ( + {!loading && lessons.map((item, index) => (
  • - - {item.order_number} + + {item.title} @@ -66,8 +66,8 @@ const CardOrders = ({ -
    OrderNumber
    +
    Title
    - { item.order_number } + { item.title }
  • @@ -90,10 +90,10 @@ const CardOrders = ({
    -
    Customer
    +
    Course
    - { dataFormatter.customersOneListFormatter(item.customer) } + { dataFormatter.coursesOneListFormatter(item.course) }
    @@ -102,10 +102,10 @@ const CardOrders = ({
    -
    Status
    +
    Content
    - { item.status } + { item.content }
    @@ -114,10 +114,17 @@ const CardOrders = ({
    -
    TotalAmount
    +
    VideoFiles
    -
    - { item.total } +
    + {dataFormatter.filesFormatter(item.video_files).map(link => ( + + ))}
    @@ -126,10 +133,10 @@ const CardOrders = ({
    -
    PlacedAt
    +
    Order
    - { dataFormatter.dateTimeFormatter(item.placed_at) } + { item.order }
    @@ -138,10 +145,10 @@ const CardOrders = ({
    -
    ShippedAt
    +
    Duration(minutes)
    - { dataFormatter.dateTimeFormatter(item.shipped_at) } + { item.duration }
    @@ -150,10 +157,10 @@ const CardOrders = ({
    -
    DeliveryDate
    +
    StartAt
    - { dataFormatter.dateTimeFormatter(item.delivery_date) } + { dataFormatter.dateTimeFormatter(item.start_at) }
    @@ -162,10 +169,10 @@ const CardOrders = ({
    -
    PaymentStatus
    +
    EndAt
    - { item.payment_status } + { dataFormatter.dateTimeFormatter(item.end_at) }
    @@ -174,10 +181,10 @@ const CardOrders = ({
    -
    ShippingAddress
    +
    Published
    - { item.shipping_address } + { dataFormatter.booleanFormatter(item.is_published) }
    @@ -187,7 +194,7 @@ const CardOrders = ({ ))} - {!loading && orders.length === 0 && ( + {!loading && lessons.length === 0 && (

    No data to display

    @@ -204,4 +211,4 @@ const CardOrders = ({ ); }; -export default CardOrders; +export default CardLessons; diff --git a/frontend/src/components/Orders/ListOrders.tsx b/frontend/src/components/Lessons/ListLessons.tsx similarity index 78% rename from frontend/src/components/Orders/ListOrders.tsx rename to frontend/src/components/Lessons/ListLessons.tsx index 2f1e56e..c79fcf7 100644 --- a/frontend/src/components/Orders/ListOrders.tsx +++ b/frontend/src/components/Lessons/ListLessons.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - orders: any[]; + lessons: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListLessons = ({ lessons, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ORDERS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LESSONS') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh <>
    {loading && } - {!loading && orders.map((item) => ( + {!loading && lessons.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -48,72 +48,79 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh
    -

    OrderNumber

    -

    { item.order_number }

    +

    Title

    +

    { item.title }

    -

    Customer

    -

    { dataFormatter.customersOneListFormatter(item.customer) }

    +

    Course

    +

    { dataFormatter.coursesOneListFormatter(item.course) }

    -

    Status

    -

    { item.status }

    +

    Content

    +

    { item.content }

    -

    TotalAmount

    -

    { item.total }

    +

    VideoFiles

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

    PlacedAt

    -

    { dataFormatter.dateTimeFormatter(item.placed_at) }

    +

    Order

    +

    { item.order }

    -

    ShippedAt

    -

    { dataFormatter.dateTimeFormatter(item.shipped_at) }

    +

    Duration(minutes)

    +

    { item.duration }

    -

    DeliveryDate

    -

    { dataFormatter.dateTimeFormatter(item.delivery_date) }

    +

    StartAt

    +

    { dataFormatter.dateTimeFormatter(item.start_at) }

    -

    PaymentStatus

    -

    { item.payment_status }

    +

    EndAt

    +

    { dataFormatter.dateTimeFormatter(item.end_at) }

    -

    ShippingAddress

    -

    { item.shipping_address }

    +

    Published

    +

    { dataFormatter.booleanFormatter(item.is_published) }

    @@ -122,8 +129,8 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh
    ))} - {!loading && orders.length === 0 && ( + {!loading && lessons.length === 0 && (

    No data to display

    @@ -149,4 +156,4 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh ) }; -export default ListOrders \ No newline at end of file +export default ListLessons \ No newline at end of file diff --git a/frontend/src/components/Shipments/TableShipments.tsx b/frontend/src/components/Lessons/TableLessons.tsx similarity index 94% rename from frontend/src/components/Shipments/TableShipments.tsx rename to frontend/src/components/Lessons/TableLessons.tsx index 23a9487..71ebfa4 100644 --- a/frontend/src/components/Shipments/TableShipments.tsx +++ b/frontend/src/components/Lessons/TableLessons.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/shipments/shipmentsSlice' +import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/lessons/lessonsSlice' 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 "./configureShipmentsCols"; +import {loadColumns} from "./configureLessonsCols"; 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 TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleLessons = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -43,7 +43,7 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid } }, ]); - const { shipments, loading, count, notify: shipmentsNotify, refetch } = useAppSelector((state) => state.shipments) + const { lessons, loading, count, notify: lessonsNotify, refetch } = useAppSelector((state) => state.lessons) 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 TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid } }; useEffect(() => { - if (shipmentsNotify.showNotification) { - notify(shipmentsNotify.typeNotification, shipmentsNotify.textNotification); + if (lessonsNotify.showNotification) { + notify(lessonsNotify.typeNotification, lessonsNotify.textNotification); } - }, [shipmentsNotify.showNotification]); + }, [lessonsNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -93,7 +93,7 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid } const handleCreateEventAction = ({ start, end }: SlotInfo) => { router.push( - `/shipments/shipments-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, + `/lessons/lessons-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, ); }; @@ -186,7 +186,7 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid } loadColumns( handleDeleteModalAction, - `shipments`, + `lessons`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -224,7 +224,7 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid } sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={shipments ?? []} + rows={lessons ?? []} columns={columns} initialState={{ pagination: { @@ -451,18 +451,18 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid } {!showGrid && ( { loadData(0,`&calendarStart=${range.start}&calendarEnd=${range.end}`); }} - entityName={'shipments'} + entityName={'lessons'} /> )} @@ -486,4 +486,4 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid } ) } -export default TableSampleShipments +export default TableSampleLessons diff --git a/frontend/src/components/Orders/configureOrdersCols.tsx b/frontend/src/components/Lessons/configureLessonsCols.tsx similarity index 75% rename from frontend/src/components/Orders/configureOrdersCols.tsx rename to frontend/src/components/Lessons/configureLessonsCols.tsx index 13f9653..29c6a9e 100644 --- a/frontend/src/components/Orders/configureOrdersCols.tsx +++ b/frontend/src/components/Lessons/configureLessonsCols.tsx @@ -37,13 +37,13 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_ORDERS') + const hasUpdatePermission = hasPermission(user, 'UPDATE_LESSONS') return [ { - field: 'order_number', - headerName: 'OrderNumber', + field: 'title', + headerName: 'Title', flex: 1, minWidth: 120, filterable: false, @@ -57,8 +57,8 @@ export const loadColumns = async ( }, { - field: 'customer', - headerName: 'Customer', + field: 'course', + headerName: 'Course', flex: 1, minWidth: 120, filterable: false, @@ -72,15 +72,15 @@ export const loadColumns = async ( type: 'singleSelect', getOptionValue: (value: any) => value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('customers'), + valueOptions: await callOptionsApi('courses'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, { - field: 'status', - headerName: 'Status', + field: 'content', + headerName: 'Content', flex: 1, minWidth: 120, filterable: false, @@ -94,8 +94,34 @@ export const loadColumns = async ( }, { - field: 'total', - headerName: 'TotalAmount', + 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: 'order', + headerName: 'Order', flex: 1, minWidth: 120, filterable: false, @@ -110,8 +136,24 @@ export const loadColumns = async ( }, { - field: 'placed_at', - headerName: 'PlacedAt', + field: 'duration', + headerName: 'Duration(minutes)', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + type: 'number', + + }, + + { + field: 'start_at', + headerName: 'StartAt', flex: 1, minWidth: 120, filterable: false, @@ -123,13 +165,13 @@ export const loadColumns = async ( type: 'dateTime', valueGetter: (params: GridValueGetterParams) => - new Date(params.row.placed_at), + new Date(params.row.start_at), }, { - field: 'shipped_at', - headerName: 'ShippedAt', + field: 'end_at', + headerName: 'EndAt', flex: 1, minWidth: 120, filterable: false, @@ -141,13 +183,13 @@ export const loadColumns = async ( type: 'dateTime', valueGetter: (params: GridValueGetterParams) => - new Date(params.row.shipped_at), + new Date(params.row.end_at), }, { - field: 'delivery_date', - headerName: 'DeliveryDate', + field: 'is_published', + headerName: 'Published', flex: 1, minWidth: 120, filterable: false, @@ -157,42 +199,10 @@ export const loadColumns = async ( editable: hasUpdatePermission, - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.delivery_date), + type: 'boolean', }, - { - field: 'payment_status', - headerName: 'PaymentStatus', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'shipping_address', - headerName: 'ShippingAddress', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - { field: 'actions', type: 'actions', @@ -206,8 +216,8 @@ export const loadColumns = async ( void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardCustomers = ({ - customers, +const CardProgress = ({ + progress, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardCustomers = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CUSTOMERS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROGRESS') return ( @@ -47,7 +47,7 @@ const CardCustomers = ({ 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 && customers.map((item, index) => ( + {!loading && progress.map((item, index) => (
  • - - {item.name} + + {item.summary} @@ -66,8 +66,8 @@ const CardCustomers = ({ -
    Name
    +
    Summary
    - { item.name } + { item.summary }
  • @@ -90,10 +90,10 @@ const CardCustomers = ({
    -
    Email
    +
    Student
    - { item.email } + { dataFormatter.usersOneListFormatter(item.student) }
    @@ -102,10 +102,10 @@ const CardCustomers = ({
    -
    Phone
    +
    Lesson
    - { item.phone } + { dataFormatter.lessonsOneListFormatter(item.lesson) }
    @@ -114,10 +114,34 @@ const CardCustomers = ({
    -
    Address
    +
    Completed
    - { item.address } + { dataFormatter.booleanFormatter(item.completed) } +
    +
    +
    + + + + +
    +
    CompletedAt
    +
    +
    + { dataFormatter.dateTimeFormatter(item.completed_at) } +
    +
    +
    + + + + +
    +
    Percent
    +
    +
    + { item.percent }
    @@ -136,34 +160,10 @@ const CardCustomers = ({ - -
    -
    VIP
    -
    -
    - { dataFormatter.booleanFormatter(item.vip) } -
    -
    -
    - - - - -
    -
    TaxNumber
    -
    -
    - { item.tax_number } -
    -
    -
    - - - ))} - {!loading && customers.length === 0 && ( + {!loading && progress.length === 0 && (

    No data to display

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

    Name

    -

    { item.name }

    +

    Summary

    +

    { item.summary }

    -

    Email

    -

    { item.email }

    +

    Student

    +

    { dataFormatter.usersOneListFormatter(item.student) }

    -

    Phone

    -

    { item.phone }

    +

    Lesson

    +

    { dataFormatter.lessonsOneListFormatter(item.lesson) }

    -

    Address

    -

    { item.address }

    +

    Completed

    +

    { dataFormatter.booleanFormatter(item.completed) }

    +
    + + + + +
    +

    CompletedAt

    +

    { dataFormatter.dateTimeFormatter(item.completed_at) }

    +
    + + + + +
    +

    Percent

    +

    { item.percent }

    @@ -86,28 +102,12 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on - -
    -

    VIP

    -

    { dataFormatter.booleanFormatter(item.vip) }

    -
    - - - - -
    -

    TaxNumber

    -

    { item.tax_number }

    -
    - - -
    ))} - {!loading && customers.length === 0 && ( + {!loading && progress.length === 0 && (

    No data to display

    @@ -133,4 +133,4 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on ) }; -export default ListCustomers \ No newline at end of file +export default ListProgress \ No newline at end of file diff --git a/frontend/src/components/Products/TableProducts.tsx b/frontend/src/components/Progress/TableProgress.tsx similarity index 97% rename from frontend/src/components/Products/TableProducts.tsx rename to frontend/src/components/Progress/TableProgress.tsx index c3ddaea..6de8caf 100644 --- a/frontend/src/components/Products/TableProducts.tsx +++ b/frontend/src/components/Progress/TableProgress.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/products/productsSlice' +import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/progress/progressSlice' 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 "./configureProductsCols"; +import {loadColumns} from "./configureProgressCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; @@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles"; const perPage = 10 -const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleProgress = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -40,7 +40,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) }, ]); - const { products, loading, count, notify: productsNotify, refetch } = useAppSelector((state) => state.products) + const { progress, loading, count, notify: progressNotify, refetch } = useAppSelector((state) => state.progress) 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 TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) }; useEffect(() => { - if (productsNotify.showNotification) { - notify(productsNotify.typeNotification, productsNotify.textNotification); + if (progressNotify.showNotification) { + notify(progressNotify.typeNotification, progressNotify.textNotification); } - }, [productsNotify.showNotification]); + }, [progressNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -177,7 +177,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) loadColumns( handleDeleteModalAction, - `products`, + `progress`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -215,7 +215,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={products ?? []} + rows={progress ?? []} columns={columns} initialState={{ pagination: { @@ -460,4 +460,4 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid }) ) } -export default TableSampleProducts +export default TableSampleProgress diff --git a/frontend/src/components/Order_items/configureOrder_itemsCols.tsx b/frontend/src/components/Progress/configureProgressCols.tsx similarity index 76% rename from frontend/src/components/Order_items/configureOrder_itemsCols.tsx rename to frontend/src/components/Progress/configureProgressCols.tsx index 3c1a0b3..b378c12 100644 --- a/frontend/src/components/Order_items/configureOrder_itemsCols.tsx +++ b/frontend/src/components/Progress/configureProgressCols.tsx @@ -37,13 +37,13 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_ORDER_ITEMS') + const hasUpdatePermission = hasPermission(user, 'UPDATE_PROGRESS') return [ { - field: 'name', - headerName: 'Name', + field: 'summary', + headerName: 'Summary', flex: 1, minWidth: 120, filterable: false, @@ -57,8 +57,8 @@ export const loadColumns = async ( }, { - field: 'order', - headerName: 'Order', + field: 'student', + headerName: 'Student', flex: 1, minWidth: 120, filterable: false, @@ -72,15 +72,15 @@ export const loadColumns = async ( type: 'singleSelect', getOptionValue: (value: any) => value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('orders'), + valueOptions: await callOptionsApi('users'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, { - field: 'product', - headerName: 'Product', + field: 'lesson', + headerName: 'Lesson', flex: 1, minWidth: 120, filterable: false, @@ -94,15 +94,49 @@ export const loadColumns = async ( type: 'singleSelect', getOptionValue: (value: any) => value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('products'), + valueOptions: await callOptionsApi('lessons'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, { - field: 'quantity', - headerName: 'Quantity', + 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: 'percent', + headerName: 'Percent', flex: 1, minWidth: 120, filterable: false, @@ -117,8 +151,8 @@ export const loadColumns = async ( }, { - field: 'unit_price', - headerName: 'UnitPrice', + field: 'notes', + headerName: 'Notes', flex: 1, minWidth: 120, filterable: false, @@ -128,24 +162,7 @@ export const loadColumns = async ( editable: hasUpdatePermission, - type: 'number', - - }, - - { - field: 'total_price', - headerName: 'TotalPrice', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, { @@ -161,8 +178,8 @@ export const loadColumns = async ( void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardOrder_items = ({ - order_items, +const CardQuiz_questions = ({ + quiz_questions, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardOrder_items = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ORDER_ITEMS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_QUIZ_QUESTIONS') return ( @@ -47,7 +47,7 @@ const CardOrder_items = ({ 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 && order_items.map((item, index) => ( + {!loading && quiz_questions.map((item, index) => (
  • - - {item.name} + + {item.question} @@ -66,8 +66,8 @@ const CardOrder_items = ({ -
    Name
    +
    Quiz
    - { item.name } + { dataFormatter.quizzesOneListFormatter(item.quiz) }
  • @@ -90,10 +90,10 @@ const CardOrder_items = ({
    -
    Order
    +
    Question
    - { dataFormatter.ordersOneListFormatter(item.order) } + { item.question }
    @@ -102,10 +102,10 @@ const CardOrder_items = ({
    -
    Product
    +
    QuestionType
    - { dataFormatter.productsOneListFormatter(item.product) } + { item.question_type }
    @@ -114,10 +114,10 @@ const CardOrder_items = ({
    -
    Quantity
    +
    Choices
    - { item.quantity } + { item.choices }
    @@ -126,22 +126,10 @@ const CardOrder_items = ({
    -
    UnitPrice
    +
    Answer
    - { item.unit_price } -
    -
    -
    - - - - -
    -
    TotalPrice
    -
    -
    - { item.total_price } + { item.answer }
    @@ -151,7 +139,7 @@ const CardOrder_items = ({ ))} - {!loading && order_items.length === 0 && ( + {!loading && quiz_questions.length === 0 && (

    No data to display

    @@ -168,4 +156,4 @@ const CardOrder_items = ({ ); }; -export default CardOrder_items; +export default CardQuiz_questions; diff --git a/frontend/src/components/Shipments/ListShipments.tsx b/frontend/src/components/Quiz_questions/ListQuiz_questions.tsx similarity index 77% rename from frontend/src/components/Shipments/ListShipments.tsx rename to frontend/src/components/Quiz_questions/ListQuiz_questions.tsx index 442af21..52a079f 100644 --- a/frontend/src/components/Shipments/ListShipments.tsx +++ b/frontend/src/components/Quiz_questions/ListQuiz_questions.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - shipments: any[]; + quiz_questions: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListQuiz_questions = ({ quiz_questions, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_SHIPMENTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_QUIZ_QUESTIONS') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on <>
    {loading && } - {!loading && shipments.map((item) => ( + {!loading && quiz_questions.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -48,48 +48,40 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on
    -

    Order

    -

    { dataFormatter.ordersOneListFormatter(item.order) }

    +

    Quiz

    +

    { dataFormatter.quizzesOneListFormatter(item.quiz) }

    -

    Carrier

    -

    { item.carrier }

    +

    Question

    +

    { item.question }

    -

    TrackingNumber

    -

    { item.tracking_number }

    +

    QuestionType

    +

    { item.question_type }

    -

    ShippedAt

    -

    { dataFormatter.dateTimeFormatter(item.shipped_at) }

    +

    Choices

    +

    { item.choices }

    -

    DeliveredAt

    -

    { dataFormatter.dateTimeFormatter(item.delivered_at) }

    -
    - - - - -
    -

    Status

    -

    { item.status }

    +

    Answer

    +

    { item.answer }

    @@ -98,8 +90,8 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on
    ))} - {!loading && shipments.length === 0 && ( + {!loading && quiz_questions.length === 0 && (

    No data to display

    @@ -125,4 +117,4 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on ) }; -export default ListShipments \ No newline at end of file +export default ListQuiz_questions \ No newline at end of file diff --git a/frontend/src/components/Payments/TablePayments.tsx b/frontend/src/components/Quiz_questions/TableQuiz_questions.tsx similarity index 96% rename from frontend/src/components/Payments/TablePayments.tsx rename to frontend/src/components/Quiz_questions/TableQuiz_questions.tsx index d5fe11d..0dbc386 100644 --- a/frontend/src/components/Payments/TablePayments.tsx +++ b/frontend/src/components/Quiz_questions/TableQuiz_questions.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/payments/paymentsSlice' +import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/quiz_questions/quiz_questionsSlice' 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 "./configurePaymentsCols"; +import {loadColumns} from "./configureQuiz_questionsCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; @@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles"; const perPage = 10 -const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleQuiz_questions = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -40,7 +40,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) }, ]); - const { payments, loading, count, notify: paymentsNotify, refetch } = useAppSelector((state) => state.payments) + const { quiz_questions, loading, count, notify: quiz_questionsNotify, refetch } = useAppSelector((state) => state.quiz_questions) 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 TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) }; useEffect(() => { - if (paymentsNotify.showNotification) { - notify(paymentsNotify.typeNotification, paymentsNotify.textNotification); + if (quiz_questionsNotify.showNotification) { + notify(quiz_questionsNotify.typeNotification, quiz_questionsNotify.textNotification); } - }, [paymentsNotify.showNotification]); + }, [quiz_questionsNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -177,7 +177,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) loadColumns( handleDeleteModalAction, - `payments`, + `quiz_questions`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -215,7 +215,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={payments ?? []} + rows={quiz_questions ?? []} columns={columns} initialState={{ pagination: { @@ -460,4 +460,4 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid }) ) } -export default TableSamplePayments +export default TableSampleQuiz_questions diff --git a/frontend/src/components/Customers/configureCustomersCols.tsx b/frontend/src/components/Quiz_questions/configureQuiz_questionsCols.tsx similarity index 71% rename from frontend/src/components/Customers/configureCustomersCols.tsx rename to frontend/src/components/Quiz_questions/configureQuiz_questionsCols.tsx index 50a553f..7ee4830 100644 --- a/frontend/src/components/Customers/configureCustomersCols.tsx +++ b/frontend/src/components/Quiz_questions/configureQuiz_questionsCols.tsx @@ -37,13 +37,35 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_CUSTOMERS') + const hasUpdatePermission = hasPermission(user, 'UPDATE_QUIZ_QUESTIONS') return [ { - field: 'name', - headerName: 'Name', + field: 'quiz', + headerName: 'Quiz', + 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('quizzes'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + + }, + + { + field: 'question', + headerName: 'Question', flex: 1, minWidth: 120, filterable: false, @@ -57,8 +79,8 @@ export const loadColumns = async ( }, { - field: 'email', - headerName: 'Email', + field: 'question_type', + headerName: 'QuestionType', flex: 1, minWidth: 120, filterable: false, @@ -72,8 +94,8 @@ export const loadColumns = async ( }, { - field: 'phone', - headerName: 'Phone', + field: 'choices', + headerName: 'Choices', flex: 1, minWidth: 120, filterable: false, @@ -87,54 +109,8 @@ export const loadColumns = async ( }, { - field: 'address', - headerName: 'Address', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'notes', - headerName: 'Notes', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - - }, - - { - field: 'vip', - headerName: 'VIP', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'boolean', - - }, - - { - field: 'tax_number', - headerName: 'TaxNumber', + field: 'answer', + headerName: 'Answer', flex: 1, minWidth: 120, filterable: false, @@ -160,8 +136,8 @@ export const loadColumns = async ( void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardPayments = ({ - payments, +const CardQuizzes = ({ + quizzes, loading, onDelete, currentPage, @@ -37,7 +37,7 @@ const CardPayments = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAYMENTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_QUIZZES') return ( @@ -47,7 +47,7 @@ const CardPayments = ({ 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 && payments.map((item, index) => ( + {!loading && quizzes.map((item, index) => (
  • - - {item.reference} + + {item.title} @@ -66,8 +66,8 @@ const CardPayments = ({ -
    Reference
    +
    Title
    - { item.reference } + { item.title }
  • @@ -90,10 +90,10 @@ const CardPayments = ({
    -
    Order
    +
    Lesson
    - { dataFormatter.ordersOneListFormatter(item.order) } + { dataFormatter.lessonsOneListFormatter(item.lesson) }
    @@ -102,10 +102,10 @@ const CardPayments = ({
    -
    Amount
    +
    Description
    - { item.amount } + { item.description }
    @@ -114,10 +114,10 @@ const CardPayments = ({
    -
    Method
    +
    PassingScore
    - { item.method } + { item.passing_score }
    @@ -126,10 +126,10 @@ const CardPayments = ({
    -
    Status
    +
    TimeLimit(minutes)
    - { item.status } + { item.time_limit }
    @@ -138,10 +138,10 @@ const CardPayments = ({
    -
    PaidAt
    +
    Active
    - { dataFormatter.dateTimeFormatter(item.paid_at) } + { dataFormatter.booleanFormatter(item.is_active) }
    @@ -151,7 +151,7 @@ const CardPayments = ({ ))} - {!loading && payments.length === 0 && ( + {!loading && quizzes.length === 0 && (

    No data to display

    @@ -168,4 +168,4 @@ const CardPayments = ({ ); }; -export default CardPayments; +export default CardQuizzes; diff --git a/frontend/src/components/Payments/ListPayments.tsx b/frontend/src/components/Quizzes/ListQuizzes.tsx similarity index 84% rename from frontend/src/components/Payments/ListPayments.tsx rename to frontend/src/components/Quizzes/ListQuizzes.tsx index 26ff557..a5d2304 100644 --- a/frontend/src/components/Payments/ListPayments.tsx +++ b/frontend/src/components/Quizzes/ListQuizzes.tsx @@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions"; type Props = { - payments: any[]; + quizzes: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -21,10 +21,10 @@ type Props = { onPageChange: (page: number) => void; }; -const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { +const ListQuizzes = ({ quizzes, loading, onDelete, currentPage, numPages, onPageChange }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAYMENTS') + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_QUIZZES') const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -34,13 +34,13 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa <>
    {loading && } - {!loading && payments.map((item) => ( + {!loading && quizzes.map((item) => (
    dark:divide-dark-700 overflow-x-auto' } @@ -48,48 +48,48 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa
    -

    Reference

    -

    { item.reference }

    +

    Title

    +

    { item.title }

    -

    Order

    -

    { dataFormatter.ordersOneListFormatter(item.order) }

    +

    Lesson

    +

    { dataFormatter.lessonsOneListFormatter(item.lesson) }

    -

    Amount

    -

    { item.amount }

    +

    Description

    +

    { item.description }

    -

    Method

    -

    { item.method }

    +

    PassingScore

    +

    { item.passing_score }

    -

    Status

    -

    { item.status }

    +

    TimeLimit(minutes)

    +

    { item.time_limit }

    -

    PaidAt

    -

    { dataFormatter.dateTimeFormatter(item.paid_at) }

    +

    Active

    +

    { dataFormatter.booleanFormatter(item.is_active) }

    @@ -98,8 +98,8 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa
    ))} - {!loading && payments.length === 0 && ( + {!loading && quizzes.length === 0 && (

    No data to display

    @@ -125,4 +125,4 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa ) }; -export default ListPayments \ No newline at end of file +export default ListQuizzes \ No newline at end of file diff --git a/frontend/src/components/Customers/TableCustomers.tsx b/frontend/src/components/Quizzes/TableQuizzes.tsx similarity index 94% rename from frontend/src/components/Customers/TableCustomers.tsx rename to frontend/src/components/Quizzes/TableQuizzes.tsx index cab1b04..42bfd6a 100644 --- a/frontend/src/components/Customers/TableCustomers.tsx +++ b/frontend/src/components/Quizzes/TableQuizzes.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/customers/customersSlice' +import { fetch, update, deleteItem, setRefetch, deleteItemsByIds } from '../../stores/quizzes/quizzesSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import { Field, Form, Formik } from "formik"; @@ -12,16 +12,18 @@ import { DataGrid, GridColDef, } from '@mui/x-data-grid'; -import {loadColumns} from "./configureCustomersCols"; +import {loadColumns} from "./configureQuizzesCols"; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter' import {dataGridStyles} from "../../styles"; +import ListQuizzes from './ListQuizzes'; + const perPage = 10 -const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }) => { +const TableSampleQuizzes = ({ filterItems, setFilterItems, filters, showGrid }) => { const notify = (type, msg) => toast( msg, {type, position: "bottom-center"}); const dispatch = useAppDispatch(); @@ -40,7 +42,7 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid } }, ]); - const { customers, loading, count, notify: customersNotify, refetch } = useAppSelector((state) => state.customers) + const { quizzes, loading, count, notify: quizzesNotify, refetch } = useAppSelector((state) => state.quizzes) const { currentUser } = useAppSelector((state) => state.auth); const focusRing = useAppSelector((state) => state.style.focusRingColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); @@ -60,10 +62,10 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid } }; useEffect(() => { - if (customersNotify.showNotification) { - notify(customersNotify.typeNotification, customersNotify.textNotification); + if (quizzesNotify.showNotification) { + notify(quizzesNotify.typeNotification, quizzesNotify.textNotification); } - }, [customersNotify.showNotification]); + }, [quizzesNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -177,7 +179,7 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid } loadColumns( handleDeleteModalAction, - `customers`, + `quizzes`, currentUser, ).then((newCols) => setColumns(newCols)); }, [currentUser]); @@ -215,7 +217,7 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid } sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={customers ?? []} + rows={quizzes ?? []} columns={columns} initialState={{ pagination: { @@ -440,10 +442,21 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid } - {dataGrid} + {quizzes && Array.isArray(quizzes) && !showGrid && ( + + )} + {showGrid && dataGrid} + {selectedRows.length > 0 && createPortal( @@ -460,4 +473,4 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid } ) } -export default TableSampleCustomers +export default TableSampleQuizzes diff --git a/frontend/src/components/Payments/configurePaymentsCols.tsx b/frontend/src/components/Quizzes/configureQuizzesCols.tsx similarity index 82% rename from frontend/src/components/Payments/configurePaymentsCols.tsx rename to frontend/src/components/Quizzes/configureQuizzesCols.tsx index 52fc718..9a415c4 100644 --- a/frontend/src/components/Payments/configurePaymentsCols.tsx +++ b/frontend/src/components/Quizzes/configureQuizzesCols.tsx @@ -37,13 +37,13 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_PAYMENTS') + const hasUpdatePermission = hasPermission(user, 'UPDATE_QUIZZES') return [ { - field: 'reference', - headerName: 'Reference', + field: 'title', + headerName: 'Title', flex: 1, minWidth: 120, filterable: false, @@ -57,8 +57,8 @@ export const loadColumns = async ( }, { - field: 'order', - headerName: 'Order', + field: 'lesson', + headerName: 'Lesson', flex: 1, minWidth: 120, filterable: false, @@ -72,15 +72,30 @@ export const loadColumns = async ( type: 'singleSelect', getOptionValue: (value: any) => value?.id, getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('orders'), + valueOptions: await callOptionsApi('lessons'), valueGetter: (params: GridValueGetterParams) => params?.value?.id ?? params?.value, }, { - field: 'amount', - headerName: 'Amount', + field: 'description', + headerName: 'Description', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + + editable: hasUpdatePermission, + + + }, + + { + field: 'passing_score', + headerName: 'PassingScore', flex: 1, minWidth: 120, filterable: false, @@ -95,8 +110,8 @@ export const loadColumns = async ( }, { - field: 'method', - headerName: 'Method', + field: 'time_limit', + headerName: 'TimeLimit(minutes)', flex: 1, minWidth: 120, filterable: false, @@ -106,12 +121,13 @@ export const loadColumns = async ( editable: hasUpdatePermission, - + type: 'number', + }, { - field: 'status', - headerName: 'Status', + field: 'is_active', + headerName: 'Active', flex: 1, minWidth: 120, filterable: false, @@ -121,24 +137,7 @@ export const loadColumns = async ( editable: hasUpdatePermission, - - }, - - { - field: 'paid_at', - headerName: 'PaidAt', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.paid_at), + type: 'boolean', }, @@ -155,8 +154,8 @@ export const loadColumns = async ( item.firstName) + }, + usersOneListFormatter(val) { + if (!val) return '' + return val.firstName + }, + usersManyListFormatterEdit(val) { + if (!val || !val.length) return [] + return val.map((item) => { + return {id: item.id, label: item.firstName} + }); + }, + usersOneListFormatterEdit(val) { + if (!val) return '' + return {label: val.firstName, id: val.id} + }, + rolesManyListFormatter(val) { @@ -84,86 +103,90 @@ export default { - productsManyListFormatter(val) { + course_categoriesManyListFormatter(val) { if (!val || !val.length) return [] return val.map((item) => item.name) }, - productsOneListFormatter(val) { + course_categoriesOneListFormatter(val) { if (!val) return '' return val.name }, - productsManyListFormatterEdit(val) { + course_categoriesManyListFormatterEdit(val) { if (!val || !val.length) return [] return val.map((item) => { return {id: item.id, label: item.name} }); }, - productsOneListFormatterEdit(val) { + course_categoriesOneListFormatterEdit(val) { if (!val) return '' return {label: val.name, id: val.id} }, - categoriesManyListFormatter(val) { + coursesManyListFormatter(val) { if (!val || !val.length) return [] - return val.map((item) => item.name) + return val.map((item) => item.title) }, - categoriesOneListFormatter(val) { + coursesOneListFormatter(val) { if (!val) return '' - return val.name + return val.title }, - categoriesManyListFormatterEdit(val) { + coursesManyListFormatterEdit(val) { if (!val || !val.length) return [] return val.map((item) => { - return {id: item.id, label: item.name} + return {id: item.id, label: item.title} }); }, - categoriesOneListFormatterEdit(val) { + coursesOneListFormatterEdit(val) { if (!val) return '' - return {label: val.name, id: val.id} + return {label: val.title, id: val.id} }, - customersManyListFormatter(val) { + lessonsManyListFormatter(val) { if (!val || !val.length) return [] - return val.map((item) => item.name) + return val.map((item) => item.title) }, - customersOneListFormatter(val) { + lessonsOneListFormatter(val) { if (!val) return '' - return val.name + return val.title }, - customersManyListFormatterEdit(val) { + lessonsManyListFormatterEdit(val) { if (!val || !val.length) return [] return val.map((item) => { - return {id: item.id, label: item.name} + return {id: item.id, label: item.title} }); }, - customersOneListFormatterEdit(val) { + lessonsOneListFormatterEdit(val) { if (!val) return '' - return {label: val.name, id: val.id} + return {label: val.title, id: val.id} }, - ordersManyListFormatter(val) { + + + + + quizzesManyListFormatter(val) { if (!val || !val.length) return [] - return val.map((item) => item.order_number) + return val.map((item) => item.title) }, - ordersOneListFormatter(val) { + quizzesOneListFormatter(val) { if (!val) return '' - return val.order_number + return val.title }, - ordersManyListFormatterEdit(val) { + quizzesManyListFormatterEdit(val) { if (!val || !val.length) return [] return val.map((item) => { - return {id: item.id, label: item.order_number} + return {id: item.id, label: item.title} }); }, - ordersOneListFormatterEdit(val) { + quizzesOneListFormatterEdit(val) { if (!val) return '' - return {label: val.order_number, id: val.id} + return {label: val.title, id: val.id} }, diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index ea7f104..4952315 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -7,6 +7,11 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiViewDashboardOutline, label: 'Dashboard', }, + { + href: '/catalog', + icon: icon.mdiBookOpenPageVariant, + label: 'Catalog', + }, { href: '/users/users-list', @@ -33,60 +38,76 @@ const menuAside: MenuAsideItem[] = [ permissions: 'READ_PERMISSIONS' }, { - href: '/products/products-list', - label: 'Products', + href: '/course_categories/course_categories-list', + label: 'Course categories', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiPackage' in icon ? icon['mdiPackage' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_PRODUCTS' + icon: 'mdiTag' in icon ? icon['mdiTag' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_COURSE_CATEGORIES' }, { - href: '/categories/categories-list', - label: 'Categories', + href: '/courses/courses-list', + label: 'Courses', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiShape' in icon ? icon['mdiShape' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CATEGORIES' + icon: 'mdiBookOpenVariant' in icon ? icon['mdiBookOpenVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_COURSES' }, { - href: '/customers/customers-list', - label: 'Customers', + href: '/lessons/lessons-list', + label: 'Lessons', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiAccountGroup' in icon ? icon['mdiAccountGroup' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CUSTOMERS' + icon: 'mdiPlayCircle' in icon ? icon['mdiPlayCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_LESSONS' }, { - href: '/orders/orders-list', - label: 'Orders', + href: '/enrollments/enrollments-list', + label: 'Enrollments', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiCart' in icon ? icon['mdiCart' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ORDERS' + icon: 'mdiSchool' in icon ? icon['mdiSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_ENROLLMENTS' }, { - href: '/order_items/order_items-list', - label: 'Order items', + href: '/progress/progress-list', + label: 'Progress', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ORDER_ITEMS' + icon: 'mdiCheckCircle' in icon ? icon['mdiCheckCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_PROGRESS' }, { - href: '/payments/payments-list', - label: 'Payments', + href: '/quizzes/quizzes-list', + label: 'Quizzes', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiCreditCard' in icon ? icon['mdiCreditCard' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_PAYMENTS' + icon: 'mdiClipboardList' in icon ? icon['mdiClipboardList' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_QUIZZES' }, { - href: '/shipments/shipments-list', - label: 'Shipments', + href: '/quiz_questions/quiz_questions-list', + label: 'Quiz questions', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - icon: 'mdiTruck' in icon ? icon['mdiTruck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_SHIPMENTS' + icon: 'mdiHelpCircle' in icon ? icon['mdiHelpCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_QUIZ_QUESTIONS' + }, + { + href: '/announcements/announcements-list', + label: 'Announcements', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiBullhorn' in icon ? icon['mdiBullhorn' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_ANNOUNCEMENTS' + }, + { + href: '/certificates/certificates-list', + label: 'Certificates', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: 'mdiCertificate' in icon ? icon['mdiCertificate' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_CERTIFICATES' }, { href: '/profile', diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index e1dfaea..f2e2132 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 = 'Store Operations Manager' - const description = "Manage products, customers, orders, payments, and fulfillment workflows for retail operations." + const title = 'CourseFlow LMS' + const description = "CourseFlow LMS: instructor and student workflows for courses, lessons, enrollments, and progress tracking." const url = "https://flatlogic.com/" - const image = "https://project-screens.s3.amazonaws.com/screenshots/37462/app-hero-20260114-104820.png" + const image = "https://project-screens.s3.amazonaws.com/screenshots/37465/app-hero-20260114-131957.png" const imageWidth = '1920' const imageHeight = '960' diff --git a/frontend/src/pages/announcements/[announcementsId].tsx b/frontend/src/pages/announcements/[announcementsId].tsx new file mode 100644 index 0000000..0baf118 --- /dev/null +++ b/frontend/src/pages/announcements/[announcementsId].tsx @@ -0,0 +1,482 @@ +import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' +import Head from 'next/head' +import React, { ReactElement, useEffect, useState } from 'react' +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import dayjs from "dayjs"; + +import CardBox from '../../components/CardBox' +import LayoutAuthenticated from '../../layouts/Authenticated' +import SectionMain from '../../components/SectionMain' +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' +import { getPageTitle } from '../../config' + +import { Field, Form, Formik } from 'formik' +import FormField from '../../components/FormField' +import BaseDivider from '../../components/BaseDivider' +import BaseButtons from '../../components/BaseButtons' +import BaseButton from '../../components/BaseButton' +import FormCheckRadio from '../../components/FormCheckRadio' +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' +import FormFilePicker from '../../components/FormFilePicker' +import FormImagePicker from '../../components/FormImagePicker' +import { SelectField } from "../../components/SelectField"; +import { SelectFieldMany } from "../../components/SelectFieldMany"; +import { SwitchField } from '../../components/SwitchField' +import {RichTextField} from "../../components/RichTextField"; + +import { update, fetch } from '../../stores/announcements/announcementsSlice' +import { useAppDispatch, useAppSelector } from '../../stores/hooks' +import { useRouter } from 'next/router' +import {saveFile} from "../../helpers/fileSaver"; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from "../../components/ImageField"; + + + +const EditAnnouncements = () => { + const router = useRouter() + const dispatch = useAppDispatch() + const initVals = { + + + + + + + + + + + + + + + + + + + + + + + + + course: null, + + + + + + 'title': '', + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + content: '', + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + published_at: new Date(), + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + is_active: false, + + + + + + + + + + + + + + + + } + const [initialValues, setInitialValues] = useState(initVals) + + const { announcements } = useAppSelector((state) => state.announcements) + + + const { announcementsId } = router.query + + useEffect(() => { + dispatch(fetch({ id: announcementsId })) + }, [announcementsId]) + + useEffect(() => { + if (typeof announcements === 'object') { + setInitialValues(announcements) + } + }, [announcements]) + + useEffect(() => { + if (typeof announcements === 'object') { + + const newInitialVal = {...initVals}; + + Object.keys(initVals).forEach(el => newInitialVal[el] = (announcements)[el]) + + setInitialValues(newInitialVal); + } + }, [announcements]) + + const handleSubmit = async (data) => { + await dispatch(update({ id: announcementsId, data })) + await router.push('/announcements/announcements-list') + } + + return ( + <> + + {getPageTitle('Edit announcements')} + + + + {''} + + + handleSubmit(values)} + > +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setInitialValues({...initialValues, 'published_at': date})} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + router.push('/announcements/announcements-list')}/> + + +
    +
    +
    + + ) +} + +EditAnnouncements.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ) +} + +export default EditAnnouncements diff --git a/frontend/src/pages/shipments/shipments-edit.tsx b/frontend/src/pages/announcements/announcements-edit.tsx similarity index 64% rename from frontend/src/pages/shipments/shipments-edit.tsx rename to frontend/src/pages/announcements/announcements-edit.tsx index c09da41..5531d2e 100644 --- a/frontend/src/pages/shipments/shipments-edit.tsx +++ b/frontend/src/pages/announcements/announcements-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/shipments/shipmentsSlice' +import { update, fetch } from '../../stores/announcements/announcementsSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import {saveFile} from "../../helpers/fileSaver"; @@ -34,7 +34,7 @@ import ImageField from "../../components/ImageField"; -const EditShipmentsPage = () => { +const EditAnnouncementsPage = () => { const router = useRouter() const dispatch = useAppDispatch() const initVals = { @@ -62,13 +62,13 @@ const EditShipmentsPage = () => { - order: null, + course: null, - 'carrier': '', + 'title': '', @@ -96,12 +96,12 @@ const EditShipmentsPage = () => { - 'tracking_number': '', - + content: '', + @@ -134,35 +134,7 @@ const EditShipmentsPage = () => { - shipped_at: new Date(), - - - - - - - - - - - - - - - - - - - - - - - - - - - - delivered_at: new Date(), + published_at: new Date(), @@ -192,11 +164,11 @@ const EditShipmentsPage = () => { - + is_active: false, - status: '', + @@ -211,7 +183,7 @@ const EditShipmentsPage = () => { } const [initialValues, setInitialValues] = useState(initVals) - const { shipments } = useAppSelector((state) => state.shipments) + const { announcements } = useAppSelector((state) => state.announcements) const { id } = router.query @@ -221,31 +193,31 @@ const EditShipmentsPage = () => { }, [id]) useEffect(() => { - if (typeof shipments === 'object') { - setInitialValues(shipments) + if (typeof announcements === 'object') { + setInitialValues(announcements) } - }, [shipments]) + }, [announcements]) useEffect(() => { - if (typeof shipments === 'object') { + if (typeof announcements === 'object') { const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (shipments)[el]) + Object.keys(initVals).forEach(el => newInitialVal[el] = (announcements)[el]) setInitialValues(newInitialVal); } - }, [shipments]) + }, [announcements]) const handleSubmit = async (data) => { await dispatch(update({ id: id, data })) - await router.push('/shipments/shipments-list') + await router.push('/announcements/announcements-list') } return ( <> - {getPageTitle('Edit shipments')} + {getPageTitle('Edit announcements')} - + {''} @@ -277,13 +249,13 @@ const EditShipmentsPage = () => { - + { + showField={'title'} + + + - showField={'order_number'} + @@ -323,11 +299,11 @@ const EditShipmentsPage = () => { @@ -359,13 +335,16 @@ const EditShipmentsPage = () => { - + + + + + + name='content' + id='content' + component={RichTextField} + > @@ -385,10 +364,6 @@ const EditShipmentsPage = () => { - - - - @@ -407,60 +382,17 @@ const EditShipmentsPage = () => { setInitialValues({...initialValues, 'shipped_at': date})} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - setInitialValues({...initialValues, 'delivered_at': date})} + onChange={(date) => setInitialValues({...initialValues, 'published_at': date})} /> @@ -496,18 +428,14 @@ const EditShipmentsPage = () => { - - - - - - - - - - - - + + + + @@ -515,8 +443,6 @@ const EditShipmentsPage = () => { - - @@ -528,7 +454,7 @@ const EditShipmentsPage = () => { - router.push('/shipments/shipments-list')}/> + router.push('/announcements/announcements-list')}/> @@ -538,11 +464,11 @@ const EditShipmentsPage = () => { ) } -EditShipmentsPage.getLayout = function getLayout(page: ReactElement) { +EditAnnouncementsPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -550,4 +476,4 @@ EditShipmentsPage.getLayout = function getLayout(page: ReactElement) { ) } -export default EditShipmentsPage +export default EditAnnouncementsPage diff --git a/frontend/src/pages/shipments/shipments-list.tsx b/frontend/src/pages/announcements/announcements-list.tsx similarity index 76% rename from frontend/src/pages/shipments/shipments-list.tsx rename to frontend/src/pages/announcements/announcements-list.tsx index d4e6eed..8efe10f 100644 --- a/frontend/src/pages/shipments/shipments-list.tsx +++ b/frontend/src/pages/announcements/announcements-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 TableShipments from '../../components/Shipments/TableShipments' +import TableAnnouncements from '../../components/Announcements/TableAnnouncements' 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/shipments/shipmentsSlice'; +import {setRefetch, uploadCsv} from '../../stores/announcements/announcementsSlice'; import {hasPermission} from "../../helpers/userPermissions"; -const ShipmentsTablesPage = () => { +const AnnouncementsTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -34,20 +34,20 @@ const ShipmentsTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Carrier', title: 'carrier'},{label: 'TrackingNumber', title: 'tracking_number'}, + const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Content', title: 'content'}, - {label: 'ShippedAt', title: 'shipped_at', date: 'true'},{label: 'DeliveredAt', title: 'delivered_at', date: 'true'}, + {label: 'PublishedAt', title: 'published_at', date: 'true'}, - {label: 'Order', title: 'order'}, + {label: 'Course', title: 'course'}, - {label: 'Status', title: 'status', type: 'enum', options: ['Pending','InTransit','Delivered','Returned']}, + ]); - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_SHIPMENTS'); + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ANNOUNCEMENTS'); const addFilter = () => { @@ -64,13 +64,13 @@ const ShipmentsTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getShipmentsCSV = async () => { - const response = await axios({url: '/shipments?filetype=csv', method: 'GET',responseType: 'blob'}); + const getAnnouncementsCSV = async () => { + const response = await axios({url: '/announcements?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 = 'shipmentsCSV.csv' + link.download = 'announcementsCSV.csv' link.click() }; @@ -90,15 +90,15 @@ const ShipmentsTablesPage = () => { return ( <> - {getPageTitle('Shipments')} + {getPageTitle('Announcements')} - + {''} - {hasCreatePermission && } + {hasCreatePermission && } { label='Filter' onClick={addFilter} /> - + {hasCreatePermission && ( {
    - Switch to Table + Switch to Table
    - { ) } -ShipmentsTablesPage.getLayout = function getLayout(page: ReactElement) { +AnnouncementsTablesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -167,4 +167,4 @@ ShipmentsTablesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default ShipmentsTablesPage +export default AnnouncementsTablesPage diff --git a/frontend/src/pages/announcements/announcements-new.tsx b/frontend/src/pages/announcements/announcements-new.tsx new file mode 100644 index 0000000..1a939fa --- /dev/null +++ b/frontend/src/pages/announcements/announcements-new.tsx @@ -0,0 +1,341 @@ +import { mdiAccount, mdiChartTimelineVariant, mdiMail, mdiUpload } from '@mdi/js' +import Head from 'next/head' +import React, { ReactElement } from 'react' +import CardBox from '../../components/CardBox' +import LayoutAuthenticated from '../../layouts/Authenticated' +import SectionMain from '../../components/SectionMain' +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' +import { getPageTitle } from '../../config' + +import { Field, Form, Formik } from 'formik' +import FormField from '../../components/FormField' +import BaseDivider from '../../components/BaseDivider' +import BaseButtons from '../../components/BaseButtons' +import BaseButton from '../../components/BaseButton' +import FormCheckRadio from '../../components/FormCheckRadio' +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' +import FormFilePicker from '../../components/FormFilePicker' +import FormImagePicker from '../../components/FormImagePicker' +import { SwitchField } from '../../components/SwitchField' + +import { SelectField } from '../../components/SelectField' +import { SelectFieldMany } from "../../components/SelectFieldMany"; +import {RichTextField} from "../../components/RichTextField"; + +import { create } from '../../stores/announcements/announcementsSlice' +import { useAppDispatch } from '../../stores/hooks' +import { useRouter } from 'next/router' +import moment from 'moment'; + +const initialValues = { + + + + + + + + + + + + + + course: '', + + + + + title: '', + + + + + + + + + + + + + + + + + + content: '', + + + + + + + + + + + + + + + + + + + published_at: '', + + + + + + + + + + + + + + + + + is_active: false, + + + + + + + + +} + + +const AnnouncementsNew = () => { + const router = useRouter() + const dispatch = useAppDispatch() + + + + + const handleSubmit = async (data) => { + await dispatch(create(data)) + await router.push('/announcements/announcements-list') + } + return ( + <> + + {getPageTitle('New Item')} + + + + {''} + + + handleSubmit(values)} + > +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + router.push('/announcements/announcements-list')}/> + + +
    +
    +
    + + ) +} + +AnnouncementsNew.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ) +} + +export default AnnouncementsNew diff --git a/frontend/src/pages/announcements/announcements-table.tsx b/frontend/src/pages/announcements/announcements-table.tsx new file mode 100644 index 0000000..692a49c --- /dev/null +++ b/frontend/src/pages/announcements/announcements-table.tsx @@ -0,0 +1,168 @@ +import { mdiChartTimelineVariant } from '@mdi/js' +import Head from 'next/head' +import { uniqueId } from 'lodash'; +import React, { ReactElement, useState } from 'react' +import CardBox from '../../components/CardBox' +import LayoutAuthenticated from '../../layouts/Authenticated' +import SectionMain from '../../components/SectionMain' +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' +import { getPageTitle } from '../../config' +import TableAnnouncements from '../../components/Announcements/TableAnnouncements' +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/announcements/announcementsSlice'; + + +import {hasPermission} from "../../helpers/userPermissions"; + + + +const AnnouncementsTablesPage = () => { + const [filterItems, setFilterItems] = useState([]); + const [csvFile, setCsvFile] = useState(null); + const [isModalActive, setIsModalActive] = useState(false); + const [showTableView, setShowTableView] = useState(false); + + + const { currentUser } = useAppSelector((state) => state.auth); + + + const dispatch = useAppDispatch(); + + + const [filters] = useState([{label: 'Title', title: 'title'},{label: 'Content', title: 'content'}, + + + {label: 'PublishedAt', title: 'published_at', date: 'true'}, + + + {label: 'Course', title: 'course'}, + + + + + ]); + + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_ANNOUNCEMENTS'); + + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getAnnouncementsCSV = async () => { + const response = await axios({url: '/announcements?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 = 'announcementsCSV.csv' + link.click() + }; + + const onModalConfirm = async () => { + if (!csvFile) return; + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; + + return ( + <> + + {getPageTitle('Announcements')} + + + + {''} + + + + {hasCreatePermission && } + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
    +
    + + + Back to list + + +
    +
    + + + +
    + + + + + ) +} + +AnnouncementsTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ) +} + +export default AnnouncementsTablesPage diff --git a/frontend/src/pages/payments/payments-view.tsx b/frontend/src/pages/announcements/announcements-view.tsx similarity index 70% rename from frontend/src/pages/payments/payments-view.tsx rename to frontend/src/pages/announcements/announcements-view.tsx index ef90088..2015704 100644 --- a/frontend/src/pages/payments/payments-view.tsx +++ b/frontend/src/pages/announcements/announcements-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/payments/paymentsSlice' +import { fetch } from '../../stores/announcements/announcementsSlice' 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 PaymentsView = () => { +const AnnouncementsView = () => { const router = useRouter() const dispatch = useAppDispatch() - const { payments } = useAppSelector((state) => state.payments) + const { announcements } = useAppSelector((state) => state.announcements) const { id } = router.query; @@ -42,50 +42,18 @@ const PaymentsView = () => { return ( <> - {getPageTitle('View payments')} + {getPageTitle('View announcements')} - + - - -
    -

    Reference

    -

    {payments?.reference}

    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -108,7 +76,7 @@ const PaymentsView = () => {
    -

    Order

    +

    Course

    @@ -119,11 +87,15 @@ const PaymentsView = () => { +

    {announcements?.course?.title ?? 'No data'}

    + + + + -

    {payments?.order?.order_number ?? 'No data'}

    @@ -144,15 +116,9 @@ const PaymentsView = () => { - - - - - -
    -

    Amount

    -

    {payments?.amount || 'No data'}

    +

    Title

    +

    {announcements?.title}

    @@ -173,16 +139,12 @@ const PaymentsView = () => { - - - - @@ -191,8 +153,11 @@ const PaymentsView = () => {
    -

    Method

    -

    {payments?.method ?? 'No data'}

    +

    Content

    + {announcements.content + ?

    + :

    No data

    + }
    @@ -205,28 +170,6 @@ const PaymentsView = () => { - - - - - - - - - - - - - - - - - -
    -

    Status

    -

    {payments?.status ?? 'No data'}

    -
    - @@ -250,17 +193,17 @@ const PaymentsView = () => { - - {payments.paid_at ? + {announcements.published_at ? :

    No PaidAt

    } + /> :

    No PublishedAt

    }
    @@ -279,6 +222,43 @@ const PaymentsView = () => { + + + + + + + + + + + + + + + + + + + null}} + disabled + /> + + + + + + + + + + + + + + @@ -296,7 +276,7 @@ const PaymentsView = () => { router.push('/payments/payments-list')} + onClick={() => router.push('/announcements/announcements-list')} /> @@ -304,11 +284,11 @@ const PaymentsView = () => { ); }; -PaymentsView.getLayout = function getLayout(page: ReactElement) { +AnnouncementsView.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -316,4 +296,4 @@ PaymentsView.getLayout = function getLayout(page: ReactElement) { ) } -export default PaymentsView; \ No newline at end of file +export default AnnouncementsView; \ No newline at end of file diff --git a/frontend/src/pages/catalog.tsx b/frontend/src/pages/catalog.tsx new file mode 100644 index 0000000..9bbed34 --- /dev/null +++ b/frontend/src/pages/catalog.tsx @@ -0,0 +1,66 @@ + +import { mdiBookOpenPageVariant } from '@mdi/js' +import Head from 'next/head' +import React, { ReactElement, useEffect, useState } from 'react' +import CardBox from '../components/CardBox' +import LayoutAuthenticated from '../layouts/Authenticated' +import SectionMain from '../components/SectionMain' +import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' +import { getPageTitle } from '../config' +import { useAppDispatch, useAppSelector } from '../stores/hooks' +import { fetch } from '../stores/courses/coursesSlice' +import CardCourses from '../components/Courses/CardCourses' + +const perPage = 12 + +const CatalogPage = () => { + const dispatch = useAppDispatch() + const { courses, loading, count } = useAppSelector((state) => state.courses) + const [currentPage, setCurrentPage] = useState(0) + + const numPages = Math.ceil(count / perPage) + + const loadData = (page: number) => { + setCurrentPage(page) + const query = `?page=${page}&limit=${perPage}` + dispatch(fetch({ limit: perPage, page, query })) + } + + useEffect(() => { + loadData(0) + }, [dispatch]) + + const onPageChange = (page: number) => { + loadData(page) + } + + return ( + <> + + {getPageTitle('Course Catalog')} + + + + {''} + + + + {}} // Empty function as we don't need delete + currentPage={currentPage} + numPages={numPages} + onPageChange={onPageChange} + /> + + + + ) +} + +CatalogPage.getLayout = function getLayout(page: ReactElement) { + return {page} +} + +export default CatalogPage diff --git a/frontend/src/pages/certificates/[certificatesId].tsx b/frontend/src/pages/certificates/[certificatesId].tsx new file mode 100644 index 0000000..aafd729 --- /dev/null +++ b/frontend/src/pages/certificates/[certificatesId].tsx @@ -0,0 +1,522 @@ +import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' +import Head from 'next/head' +import React, { ReactElement, useEffect, useState } from 'react' +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import dayjs from "dayjs"; + +import CardBox from '../../components/CardBox' +import LayoutAuthenticated from '../../layouts/Authenticated' +import SectionMain from '../../components/SectionMain' +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' +import { getPageTitle } from '../../config' + +import { Field, Form, Formik } from 'formik' +import FormField from '../../components/FormField' +import BaseDivider from '../../components/BaseDivider' +import BaseButtons from '../../components/BaseButtons' +import BaseButton from '../../components/BaseButton' +import FormCheckRadio from '../../components/FormCheckRadio' +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' +import FormFilePicker from '../../components/FormFilePicker' +import FormImagePicker from '../../components/FormImagePicker' +import { SelectField } from "../../components/SelectField"; +import { SelectFieldMany } from "../../components/SelectFieldMany"; +import { SwitchField } from '../../components/SwitchField' +import {RichTextField} from "../../components/RichTextField"; + +import { update, fetch } from '../../stores/certificates/certificatesSlice' +import { useAppDispatch, useAppSelector } from '../../stores/hooks' +import { useRouter } from 'next/router' +import {saveFile} from "../../helpers/fileSaver"; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from "../../components/ImageField"; + + + +const EditCertificates = () => { + const router = useRouter() + const dispatch = useAppDispatch() + const initVals = { + + + 'serial': '', + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + student: null, + + + + + + + + + + + + + + + + + + + + + + + + + + + + course: null, + + + + + + + + + + + + + + + + issued_at: new Date(), + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file: [], + + + + + + + + + + } + const [initialValues, setInitialValues] = useState(initVals) + + const { certificates } = useAppSelector((state) => state.certificates) + + + const { certificatesId } = router.query + + useEffect(() => { + dispatch(fetch({ id: certificatesId })) + }, [certificatesId]) + + useEffect(() => { + if (typeof certificates === 'object') { + setInitialValues(certificates) + } + }, [certificates]) + + useEffect(() => { + if (typeof certificates === 'object') { + + const newInitialVal = {...initVals}; + + Object.keys(initVals).forEach(el => newInitialVal[el] = (certificates)[el]) + + setInitialValues(newInitialVal); + } + }, [certificates]) + + const handleSubmit = async (data) => { + await dispatch(update({ id: certificatesId, data })) + await router.push('/certificates/certificates-list') + } + + return ( + <> + + {getPageTitle('Edit certificates')} + + + + {''} + + + handleSubmit(values)} + > +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setInitialValues({...initialValues, 'issued_at': date})} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + router.push('/certificates/certificates-list')}/> + + +
    +
    +
    + + ) +} + +EditCertificates.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ) +} + +export default EditCertificates diff --git a/frontend/src/pages/certificates/certificates-edit.tsx b/frontend/src/pages/certificates/certificates-edit.tsx new file mode 100644 index 0000000..48d1cb0 --- /dev/null +++ b/frontend/src/pages/certificates/certificates-edit.tsx @@ -0,0 +1,519 @@ +import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js' +import Head from 'next/head' +import React, { ReactElement, useEffect, useState } from 'react' +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import dayjs from "dayjs"; + +import CardBox from '../../components/CardBox' +import LayoutAuthenticated from '../../layouts/Authenticated' +import SectionMain from '../../components/SectionMain' +import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' +import { getPageTitle } from '../../config' + +import { Field, Form, Formik } from 'formik' +import FormField from '../../components/FormField' +import BaseDivider from '../../components/BaseDivider' +import BaseButtons from '../../components/BaseButtons' +import BaseButton from '../../components/BaseButton' +import FormCheckRadio from '../../components/FormCheckRadio' +import FormCheckRadioGroup from '../../components/FormCheckRadioGroup' +import FormFilePicker from '../../components/FormFilePicker' +import FormImagePicker from '../../components/FormImagePicker' +import { SelectField } from "../../components/SelectField"; +import { SelectFieldMany } from "../../components/SelectFieldMany"; +import { SwitchField } from '../../components/SwitchField' +import {RichTextField} from "../../components/RichTextField"; + +import { update, fetch } from '../../stores/certificates/certificatesSlice' +import { useAppDispatch, useAppSelector } from '../../stores/hooks' +import { useRouter } from 'next/router' +import {saveFile} from "../../helpers/fileSaver"; +import dataFormatter from '../../helpers/dataFormatter'; +import ImageField from "../../components/ImageField"; + + + +const EditCertificatesPage = () => { + const router = useRouter() + const dispatch = useAppDispatch() + const initVals = { + + + 'serial': '', + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + student: null, + + + + + + + + + + + + + + + + + + + + + + + + + + + + course: null, + + + + + + + + + + + + + + + + issued_at: new Date(), + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + file: [], + + + + + + + + + + } + const [initialValues, setInitialValues] = useState(initVals) + + const { certificates } = useAppSelector((state) => state.certificates) + + + const { id } = router.query + + useEffect(() => { + dispatch(fetch({ id: id })) + }, [id]) + + useEffect(() => { + if (typeof certificates === 'object') { + setInitialValues(certificates) + } + }, [certificates]) + + useEffect(() => { + if (typeof certificates === 'object') { + const newInitialVal = {...initVals}; + Object.keys(initVals).forEach(el => newInitialVal[el] = (certificates)[el]) + setInitialValues(newInitialVal); + } + }, [certificates]) + + const handleSubmit = async (data) => { + await dispatch(update({ id: id, data })) + await router.push('/certificates/certificates-list') + } + + return ( + <> + + {getPageTitle('Edit certificates')} + + + + {''} + + + handleSubmit(values)} + > +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + setInitialValues({...initialValues, 'issued_at': date})} + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + router.push('/certificates/certificates-list')}/> + + +
    +
    +
    + + ) +} + +EditCertificatesPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ) +} + +export default EditCertificatesPage diff --git a/frontend/src/pages/customers/customers-list.tsx b/frontend/src/pages/certificates/certificates-list.tsx similarity index 79% rename from frontend/src/pages/customers/customers-list.tsx rename to frontend/src/pages/certificates/certificates-list.tsx index a4d4a5c..4b781e5 100644 --- a/frontend/src/pages/customers/customers-list.tsx +++ b/frontend/src/pages/certificates/certificates-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 TableCustomers from '../../components/Customers/TableCustomers' +import TableCertificates from '../../components/Certificates/TableCertificates' 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/customers/customersSlice'; +import {setRefetch, uploadCsv} from '../../stores/certificates/certificatesSlice'; import {hasPermission} from "../../helpers/userPermissions"; -const CustomersTablesPage = () => { +const CertificatesTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -34,16 +34,24 @@ const CustomersTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Name', title: 'name'},{label: 'Email', title: 'email'},{label: 'Phone', title: 'phone'},{label: 'Address', title: 'address'},{label: 'Notes', title: 'notes'},{label: 'TaxNumber', title: 'tax_number'}, - + const [filters] = useState([{label: 'Serial', title: 'serial'}, + {label: 'IssuedAt', title: 'issued_at', date: 'true'}, + + + {label: 'Student', title: 'student'}, + + + + {label: 'Course', title: 'course'}, + ]); - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CUSTOMERS'); + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CERTIFICATES'); const addFilter = () => { @@ -60,13 +68,13 @@ const CustomersTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getCustomersCSV = async () => { - const response = await axios({url: '/customers?filetype=csv', method: 'GET',responseType: 'blob'}); + const getCertificatesCSV = async () => { + const response = await axios({url: '/certificates?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 = 'customersCSV.csv' + link.download = 'certificatesCSV.csv' link.click() }; @@ -86,15 +94,15 @@ const CustomersTablesPage = () => { return ( <> - {getPageTitle('Customers')} + {getPageTitle('Certificates')} - + {''} - {hasCreatePermission && } + {hasCreatePermission && } { label='Filter' onClick={addFilter} /> - + {hasCreatePermission && ( { - { ) } -CustomersTablesPage.getLayout = function getLayout(page: ReactElement) { +CertificatesTablesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -159,4 +167,4 @@ CustomersTablesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default CustomersTablesPage +export default CertificatesTablesPage diff --git a/frontend/src/pages/payments/payments-new.tsx b/frontend/src/pages/certificates/certificates-new.tsx similarity index 64% rename from frontend/src/pages/payments/payments-new.tsx rename to frontend/src/pages/certificates/certificates-new.tsx index 5e08fc6..ba4bdc1 100644 --- a/frontend/src/pages/payments/payments-new.tsx +++ b/frontend/src/pages/certificates/certificates-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/payments/paymentsSlice' +import { create } from '../../stores/certificates/certificatesSlice' import { useAppDispatch } from '../../stores/hooks' import { useRouter } from 'next/router' import moment from 'moment'; @@ -30,7 +30,7 @@ import moment from 'moment'; const initialValues = { - reference: '', + serial: '', @@ -57,12 +57,11 @@ const initialValues = { - order: '', + student: '', - amount: '', @@ -74,6 +73,7 @@ const initialValues = { + course: '', @@ -83,11 +83,11 @@ const initialValues = { + issued_at: '', - method: 'Card', @@ -103,25 +103,7 @@ const initialValues = { - - status: 'Pending', - - - - - - - - - - - - - paid_at: '', - - - - + file: [], @@ -130,7 +112,7 @@ const initialValues = { } -const PaymentsNew = () => { +const CertificatesNew = () => { const router = useRouter() const dispatch = useAppDispatch() @@ -139,7 +121,7 @@ const PaymentsNew = () => { const handleSubmit = async (data) => { await dispatch(create(data)) - await router.push('/payments/payments-list') + await router.push('/certificates/certificates-list') } return ( <> @@ -164,11 +146,11 @@ const PaymentsNew = () => { @@ -216,80 +198,8 @@ const PaymentsNew = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -318,18 +228,8 @@ const PaymentsNew = () => { - - - - - - - - - - - - + + @@ -347,20 +247,16 @@ const PaymentsNew = () => { - - - - @@ -378,11 +274,53 @@ const PaymentsNew = () => { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - router.push('/payments/payments-list')}/> + router.push('/certificates/certificates-list')}/> @@ -392,11 +330,11 @@ const PaymentsNew = () => { ) } -PaymentsNew.getLayout = function getLayout(page: ReactElement) { +CertificatesNew.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -404,4 +342,4 @@ PaymentsNew.getLayout = function getLayout(page: ReactElement) { ) } -export default PaymentsNew +export default CertificatesNew diff --git a/frontend/src/pages/customers/customers-table.tsx b/frontend/src/pages/certificates/certificates-table.tsx similarity index 78% rename from frontend/src/pages/customers/customers-table.tsx rename to frontend/src/pages/certificates/certificates-table.tsx index 7143423..c2707cb 100644 --- a/frontend/src/pages/customers/customers-table.tsx +++ b/frontend/src/pages/certificates/certificates-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 TableCustomers from '../../components/Customers/TableCustomers' +import TableCertificates from '../../components/Certificates/TableCertificates' 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/customers/customersSlice'; +import {setRefetch, uploadCsv} from '../../stores/certificates/certificatesSlice'; import {hasPermission} from "../../helpers/userPermissions"; -const CustomersTablesPage = () => { +const CertificatesTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -34,16 +34,24 @@ const CustomersTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Name', title: 'name'},{label: 'Email', title: 'email'},{label: 'Phone', title: 'phone'},{label: 'Address', title: 'address'},{label: 'Notes', title: 'notes'},{label: 'TaxNumber', title: 'tax_number'}, - + const [filters] = useState([{label: 'Serial', title: 'serial'}, + {label: 'IssuedAt', title: 'issued_at', date: 'true'}, + + + {label: 'Student', title: 'student'}, + + + + {label: 'Course', title: 'course'}, + ]); - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CUSTOMERS'); + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CERTIFICATES'); const addFilter = () => { @@ -60,13 +68,13 @@ const CustomersTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getCustomersCSV = async () => { - const response = await axios({url: '/customers?filetype=csv', method: 'GET',responseType: 'blob'}); + const getCertificatesCSV = async () => { + const response = await axios({url: '/certificates?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 = 'customersCSV.csv' + link.download = 'certificatesCSV.csv' link.click() }; @@ -86,15 +94,15 @@ const CustomersTablesPage = () => { return ( <> - {getPageTitle('Customers')} + {getPageTitle('Certificates')} - + {''} - {hasCreatePermission && } + {hasCreatePermission && } { label='Filter' onClick={addFilter} /> - + {hasCreatePermission && ( {
    - + Back to table
    - { ) } -CustomersTablesPage.getLayout = function getLayout(page: ReactElement) { +CertificatesTablesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -161,4 +169,4 @@ CustomersTablesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default CustomersTablesPage +export default CertificatesTablesPage diff --git a/frontend/src/pages/order_items/order_items-view.tsx b/frontend/src/pages/certificates/certificates-view.tsx similarity index 67% rename from frontend/src/pages/order_items/order_items-view.tsx rename to frontend/src/pages/certificates/certificates-view.tsx index 79847a0..cc0700b 100644 --- a/frontend/src/pages/order_items/order_items-view.tsx +++ b/frontend/src/pages/certificates/certificates-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/order_items/order_itemsSlice' +import { fetch } from '../../stores/certificates/certificatesSlice' 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 Order_itemsView = () => { +const CertificatesView = () => { const router = useRouter() const dispatch = useAppDispatch() - const { order_items } = useAppSelector((state) => state.order_items) + const { certificates } = useAppSelector((state) => state.certificates) const { id } = router.query; @@ -42,14 +42,14 @@ const Order_itemsView = () => { return ( <> - {getPageTitle('View order_items')} + {getPageTitle('View certificates')} - + @@ -57,8 +57,8 @@ const Order_itemsView = () => {
    -

    Name

    -

    {order_items?.name}

    +

    Serial

    +

    {certificates?.serial}

    @@ -108,7 +108,12 @@ const Order_itemsView = () => {
    -

    Order

    +

    Student

    + + +

    {certificates?.student?.firstName ?? 'No data'}

    + + @@ -123,7 +128,6 @@ const Order_itemsView = () => { -

    {order_items?.order?.order_number ?? 'No data'}

    @@ -164,7 +168,7 @@ const Order_itemsView = () => {
    -

    Product

    +

    Course

    @@ -173,7 +177,11 @@ const Order_itemsView = () => { -

    {order_items?.product?.name ?? 'No data'}

    + + +

    {certificates?.course?.title ?? 'No data'}

    + + @@ -197,6 +205,64 @@ const Order_itemsView = () => { + + + + + + + + + + + + + + + {certificates.issued_at ? :

    No IssuedAt

    } +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -207,92 +273,19 @@ const Order_itemsView = () => {
    -

    Quantity

    -

    {order_items?.quantity || 'No data'}

    +

    File

    + {certificates?.file?.length + ? dataFormatter.filesFormatter(certificates.file).map(link => ( + + )) :

    No File

    + }
    - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    UnitPrice

    -

    {order_items?.unit_price || 'No data'}

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

    TotalPrice

    -

    {order_items?.total_price || 'No data'}

    -
    - - - - - - - - - - - - - - - - - - - @@ -306,13 +299,15 @@ const Order_itemsView = () => { + + router.push('/order_items/order_items-list')} + onClick={() => router.push('/certificates/certificates-list')} /> @@ -320,11 +315,11 @@ const Order_itemsView = () => { ); }; -Order_itemsView.getLayout = function getLayout(page: ReactElement) { +CertificatesView.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -332,4 +327,4 @@ Order_itemsView.getLayout = function getLayout(page: ReactElement) { ) } -export default Order_itemsView; \ No newline at end of file +export default CertificatesView; \ No newline at end of file diff --git a/frontend/src/pages/categories/[categoriesId].tsx b/frontend/src/pages/course_categories/[course_categoriesId].tsx similarity index 73% rename from frontend/src/pages/categories/[categoriesId].tsx rename to frontend/src/pages/course_categories/[course_categoriesId].tsx index 0f27700..972c296 100644 --- a/frontend/src/pages/categories/[categoriesId].tsx +++ b/frontend/src/pages/course_categories/[course_categoriesId].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/categories/categoriesSlice' +import { update, fetch } from '../../stores/course_categories/course_categoriesSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import {saveFile} from "../../helpers/fileSaver"; @@ -34,7 +34,7 @@ import ImageField from "../../components/ImageField"; -const EditCategories = () => { +const EditCourse_categories = () => { const router = useRouter() const dispatch = useAppDispatch() const initVals = { @@ -60,6 +60,34 @@ const EditCategories = () => { + + + + + + + + + 'slug': '', + + + + + + + + + + + + + + + + + + + @@ -96,75 +124,47 @@ const EditCategories = () => { - - - - - - - - - - - - - - - - - - - - - - - parent: null, - - - - - } const [initialValues, setInitialValues] = useState(initVals) - const { categories } = useAppSelector((state) => state.categories) + const { course_categories } = useAppSelector((state) => state.course_categories) - const { categoriesId } = router.query + const { course_categoriesId } = router.query useEffect(() => { - dispatch(fetch({ id: categoriesId })) - }, [categoriesId]) + dispatch(fetch({ id: course_categoriesId })) + }, [course_categoriesId]) useEffect(() => { - if (typeof categories === 'object') { - setInitialValues(categories) + if (typeof course_categories === 'object') { + setInitialValues(course_categories) } - }, [categories]) + }, [course_categories]) useEffect(() => { - if (typeof categories === 'object') { + if (typeof course_categories === 'object') { const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (categories)[el]) + Object.keys(initVals).forEach(el => newInitialVal[el] = (course_categories)[el]) setInitialValues(newInitialVal); } - }, [categories]) + }, [course_categories]) const handleSubmit = async (data) => { - await dispatch(update({ id: categoriesId, data })) - await router.push('/categories/categories-list') + await dispatch(update({ id: course_categoriesId, data })) + await router.push('/course_categories/course_categories-list') } return ( <> - {getPageTitle('Edit categories')} + {getPageTitle('Edit course_categories')} - + {''} @@ -204,6 +204,43 @@ const EditCategories = () => { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -244,77 +281,13 @@ const EditCategories = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/categories/categories-list')}/> + router.push('/course_categories/course_categories-list')}/> @@ -324,11 +297,11 @@ const EditCategories = () => { ) } -EditCategories.getLayout = function getLayout(page: ReactElement) { +EditCourse_categories.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -336,4 +309,4 @@ EditCategories.getLayout = function getLayout(page: ReactElement) { ) } -export default EditCategories +export default EditCourse_categories diff --git a/frontend/src/pages/categories/categories-edit.tsx b/frontend/src/pages/course_categories/course_categories-edit.tsx similarity index 76% rename from frontend/src/pages/categories/categories-edit.tsx rename to frontend/src/pages/course_categories/course_categories-edit.tsx index c168aa8..a275528 100644 --- a/frontend/src/pages/categories/categories-edit.tsx +++ b/frontend/src/pages/course_categories/course_categories-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/categories/categoriesSlice' +import { update, fetch } from '../../stores/course_categories/course_categoriesSlice' import { useAppDispatch, useAppSelector } from '../../stores/hooks' import { useRouter } from 'next/router' import {saveFile} from "../../helpers/fileSaver"; @@ -34,7 +34,7 @@ import ImageField from "../../components/ImageField"; -const EditCategoriesPage = () => { +const EditCourse_categoriesPage = () => { const router = useRouter() const dispatch = useAppDispatch() const initVals = { @@ -60,6 +60,34 @@ const EditCategoriesPage = () => { + + + + + + + + + 'slug': '', + + + + + + + + + + + + + + + + + + + @@ -96,38 +124,10 @@ const EditCategoriesPage = () => { - - - - - - - - - - - - - - - - - - - - - - - parent: null, - - - - - } const [initialValues, setInitialValues] = useState(initVals) - const { categories } = useAppSelector((state) => state.categories) + const { course_categories } = useAppSelector((state) => state.course_categories) const { id } = router.query @@ -137,31 +137,31 @@ const EditCategoriesPage = () => { }, [id]) useEffect(() => { - if (typeof categories === 'object') { - setInitialValues(categories) + if (typeof course_categories === 'object') { + setInitialValues(course_categories) } - }, [categories]) + }, [course_categories]) useEffect(() => { - if (typeof categories === 'object') { + if (typeof course_categories === 'object') { const newInitialVal = {...initVals}; - Object.keys(initVals).forEach(el => newInitialVal[el] = (categories)[el]) + Object.keys(initVals).forEach(el => newInitialVal[el] = (course_categories)[el]) setInitialValues(newInitialVal); } - }, [categories]) + }, [course_categories]) const handleSubmit = async (data) => { await dispatch(update({ id: id, data })) - await router.push('/categories/categories-list') + await router.push('/course_categories/course_categories-list') } return ( <> - {getPageTitle('Edit categories')} + {getPageTitle('Edit course_categories')} - + {''} @@ -201,6 +201,43 @@ const EditCategoriesPage = () => { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -241,77 +278,13 @@ const EditCategoriesPage = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/categories/categories-list')}/> + router.push('/course_categories/course_categories-list')}/> @@ -321,11 +294,11 @@ const EditCategoriesPage = () => { ) } -EditCategoriesPage.getLayout = function getLayout(page: ReactElement) { +EditCourse_categoriesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -333,4 +306,4 @@ EditCategoriesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default EditCategoriesPage +export default EditCourse_categoriesPage diff --git a/frontend/src/pages/categories/categories-list.tsx b/frontend/src/pages/course_categories/course_categories-list.tsx similarity index 79% rename from frontend/src/pages/categories/categories-list.tsx rename to frontend/src/pages/course_categories/course_categories-list.tsx index 2618de6..b772ac7 100644 --- a/frontend/src/pages/categories/categories-list.tsx +++ b/frontend/src/pages/course_categories/course_categories-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 TableCategories from '../../components/Categories/TableCategories' +import TableCourse_categories from '../../components/Course_categories/TableCourse_categories' 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/categories/categoriesSlice'; +import {setRefetch, uploadCsv} from '../../stores/course_categories/course_categoriesSlice'; import {hasPermission} from "../../helpers/userPermissions"; -const CategoriesTablesPage = () => { +const Course_categoriesTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -34,20 +34,16 @@ const CategoriesTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Name', title: 'name'},{label: 'Description', title: 'description'}, + const [filters] = useState([{label: 'Name', title: 'name'},{label: 'Slug', title: 'slug'},{label: 'Description', title: 'description'}, - - {label: 'ParentCategory', title: 'parent'}, - - ]); - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CATEGORIES'); + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_COURSE_CATEGORIES'); const addFilter = () => { @@ -64,13 +60,13 @@ const CategoriesTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getCategoriesCSV = async () => { - const response = await axios({url: '/categories?filetype=csv', method: 'GET',responseType: 'blob'}); + const getCourse_categoriesCSV = async () => { + const response = await axios({url: '/course_categories?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 = 'categoriesCSV.csv' + link.download = 'course_categoriesCSV.csv' link.click() }; @@ -90,15 +86,15 @@ const CategoriesTablesPage = () => { return ( <> - {getPageTitle('Categories')} + {getPageTitle('Course_categories')} - + {''} - {hasCreatePermission && } + {hasCreatePermission && } { label='Filter' onClick={addFilter} /> - + {hasCreatePermission && ( {
    - Switch to Table + Switch to Table
    - { ) } -CategoriesTablesPage.getLayout = function getLayout(page: ReactElement) { +Course_categoriesTablesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -167,4 +163,4 @@ CategoriesTablesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default CategoriesTablesPage +export default Course_categoriesTablesPage diff --git a/frontend/src/pages/categories/categories-new.tsx b/frontend/src/pages/course_categories/course_categories-new.tsx similarity index 85% rename from frontend/src/pages/categories/categories-new.tsx rename to frontend/src/pages/course_categories/course_categories-new.tsx index b957e7b..f700e7d 100644 --- a/frontend/src/pages/categories/categories-new.tsx +++ b/frontend/src/pages/course_categories/course_categories-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/categories/categoriesSlice' +import { create } from '../../stores/course_categories/course_categoriesSlice' import { useAppDispatch } from '../../stores/hooks' import { useRouter } from 'next/router' import moment from 'moment'; @@ -46,6 +46,22 @@ const initialValues = { + slug: '', + + + + + + + + + + + + + + + description: '', @@ -61,26 +77,10 @@ const initialValues = { - - - - - - - - - - - - - parent: '', - - - } -const CategoriesNew = () => { +const Course_categoriesNew = () => { const router = useRouter() const dispatch = useAppDispatch() @@ -89,7 +89,7 @@ const CategoriesNew = () => { const handleSubmit = async (data) => { await dispatch(create(data)) - await router.push('/categories/categories-list') + await router.push('/course_categories/course_categories-list') } return ( <> @@ -141,6 +141,41 @@ const CategoriesNew = () => { + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -175,42 +210,12 @@ const CategoriesNew = () => { - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/categories/categories-list')}/> + router.push('/course_categories/course_categories-list')}/> @@ -220,11 +225,11 @@ const CategoriesNew = () => { ) } -CategoriesNew.getLayout = function getLayout(page: ReactElement) { +Course_categoriesNew.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -232,4 +237,4 @@ CategoriesNew.getLayout = function getLayout(page: ReactElement) { ) } -export default CategoriesNew +export default Course_categoriesNew diff --git a/frontend/src/pages/categories/categories-table.tsx b/frontend/src/pages/course_categories/course_categories-table.tsx similarity index 79% rename from frontend/src/pages/categories/categories-table.tsx rename to frontend/src/pages/course_categories/course_categories-table.tsx index 1f045f0..9fb0419 100644 --- a/frontend/src/pages/categories/categories-table.tsx +++ b/frontend/src/pages/course_categories/course_categories-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 TableCategories from '../../components/Categories/TableCategories' +import TableCourse_categories from '../../components/Course_categories/TableCourse_categories' 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/categories/categoriesSlice'; +import {setRefetch, uploadCsv} from '../../stores/course_categories/course_categoriesSlice'; import {hasPermission} from "../../helpers/userPermissions"; -const CategoriesTablesPage = () => { +const Course_categoriesTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -34,20 +34,16 @@ const CategoriesTablesPage = () => { const dispatch = useAppDispatch(); - const [filters] = useState([{label: 'Name', title: 'name'},{label: 'Description', title: 'description'}, + const [filters] = useState([{label: 'Name', title: 'name'},{label: 'Slug', title: 'slug'},{label: 'Description', title: 'description'}, - - {label: 'ParentCategory', title: 'parent'}, - - ]); - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_CATEGORIES'); + const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_COURSE_CATEGORIES'); const addFilter = () => { @@ -64,13 +60,13 @@ const CategoriesTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getCategoriesCSV = async () => { - const response = await axios({url: '/categories?filetype=csv', method: 'GET',responseType: 'blob'}); + const getCourse_categoriesCSV = async () => { + const response = await axios({url: '/course_categories?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 = 'categoriesCSV.csv' + link.download = 'course_categoriesCSV.csv' link.click() }; @@ -90,15 +86,15 @@ const CategoriesTablesPage = () => { return ( <> - {getPageTitle('Categories')} + {getPageTitle('Course_categories')} - + {''} - {hasCreatePermission && } + {hasCreatePermission && } { label='Filter' onClick={addFilter} /> - + {hasCreatePermission && ( {
    - + Back to list
    - { ) } -CategoriesTablesPage.getLayout = function getLayout(page: ReactElement) { +Course_categoriesTablesPage.getLayout = function getLayout(page: ReactElement) { return ( {page} @@ -165,4 +161,4 @@ CategoriesTablesPage.getLayout = function getLayout(page: ReactElement) { ) } -export default CategoriesTablesPage +export default Course_categoriesTablesPage diff --git a/frontend/src/pages/categories/categories-view.tsx b/frontend/src/pages/course_categories/course_categories-view.tsx similarity index 62% rename from frontend/src/pages/categories/categories-view.tsx rename to frontend/src/pages/course_categories/course_categories-view.tsx index ac846be..321afc3 100644 --- a/frontend/src/pages/categories/categories-view.tsx +++ b/frontend/src/pages/course_categories/course_categories-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/categories/categoriesSlice' +import { fetch } from '../../stores/course_categories/course_categoriesSlice' 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 CategoriesView = () => { +const Course_categoriesView = () => { const router = useRouter() const dispatch = useAppDispatch() - const { categories } = useAppSelector((state) => state.categories) + const { course_categories } = useAppSelector((state) => state.course_categories) const { id } = router.query; @@ -42,14 +42,14 @@ const CategoriesView = () => { return ( <> - {getPageTitle('View categories')} + {getPageTitle('View course_categories')} - + @@ -58,7 +58,39 @@ const CategoriesView = () => {

    Name

    -

    {categories?.name}

    +

    {course_categories?.name}

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    Slug

    +

    {course_categories?.slug}

    @@ -91,7 +123,7 @@ const CategoriesView = () => { -