Forced merge: merge ai-dev into master

This commit is contained in:
Flatlogic Bot 2026-01-14 13:27:54 +00:00
commit bb2fbfd4ad
180 changed files with 22474 additions and 10494 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
/backend/node_modules
/frontend/node_modules
node_modules/ node_modules/
*/node_modules/ */node_modules/
**/node_modules/
*/build/ */build/

View File

@ -129,8 +129,8 @@
<p class="tip">The application is currently launching. The page will automatically refresh once site is <p class="tip">The application is currently launching. The page will automatically refresh once site is
available.</p> available.</p>
<div class="project-info"> <div class="project-info">
<h2>Store Operations Manager</h2> <h2>CourseFlow LMS</h2>
<p>Manage products, customers, orders, payments, and fulfillment workflows for retail operations.</p> <p>CourseFlow LMS: instructor and student workflows for courses, lessons, enrollments, and progress tracking.</p>
</div> </div>
<div class="loader-container"> <div class="loader-container">
<img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo" <img src="https://flatlogic.com/blog/wp-content/uploads/2025/05/logo-bot-1.png" alt="App Logo"

View File

@ -1,6 +1,6 @@
# Store Operations Manager # CourseFlow LMS
## This project was generated by [Flatlogic Platform](https://flatlogic.com). ## This project was generated by [Flatlogic Platform](https://flatlogic.com).

View File

@ -1,6 +1,6 @@
DB_NAME=app_37462 DB_NAME=app_37465
DB_USER=app_37462 DB_USER=app_37465
DB_PASS=b5fc3cee-cc82-4cf6-bbcd-51bd3a434c2c DB_PASS=578d9f61-c443-4a62-87c0-ac39c47254f1
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=5432 DB_PORT=5432
PORT=3000 PORT=3000

View File

@ -1,5 +1,5 @@
#Store Operations Manager - template backend, #CourseFlow LMS - template backend,
#### Run App on local machine: #### Run App on local machine:
@ -30,10 +30,10 @@
- `psql postgres -U admin` - `psql postgres -U admin`
- Type this command to creating a new database. - Type this command to creating a new database.
- `postgres=> 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`. - 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` - `postgres=> \q`
------------ ------------

View File

@ -1,6 +1,6 @@
{ {
"name": "storeoperationsmanager", "name": "courseflowlms",
"description": "Store Operations Manager - template backend", "description": "CourseFlow LMS - template backend",
"scripts": { "scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch", "start": "npm run db:migrate && npm run db:seed && npm run watch",
"db:migrate": "sequelize-cli db:migrate", "db:migrate": "sequelize-cli db:migrate",

View File

@ -11,15 +11,15 @@ const config = {
bcrypt: { bcrypt: {
saltRounds: 12 saltRounds: 12
}, },
admin_pass: "b5fc3cee", admin_pass: "578d9f61",
user_pass: "51bd3a434c2c", user_pass: "ac39c47254f1",
admin_email: "admin@flatlogic.com", admin_email: "admin@flatlogic.com",
providers: { providers: {
LOCAL: 'local', LOCAL: 'local',
GOOGLE: 'google', GOOGLE: 'google',
MICROSOFT: 'microsoft' 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: '', remote: '',
port: process.env.NODE_ENV === "production" ? "" : "8080", port: process.env.NODE_ENV === "production" ? "" : "8080",
hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost", hostUI: process.env.NODE_ENV === "production" ? "" : "http://localhost",
@ -39,7 +39,7 @@ const config = {
}, },
uploadDir: os.tmpdir(), uploadDir: os.tmpdir(),
email: { email: {
from: 'Store Operations Manager <app@flatlogic.app>', from: 'CourseFlow LMS <app@flatlogic.app>',
host: 'email-smtp.us-east-1.amazonaws.com', host: 'email-smtp.us-east-1.amazonaws.com',
port: 587, port: 587,
auth: { 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', 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.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.host = process.env.NODE_ENV === "production" ? config.remote : "http://localhost";
config.apiUrl = `${config.host}${config.port ? `:${config.port}` : ``}/api`; config.apiUrl = `${config.host}${config.port ? `:${config.port}` : ``}/api`;
config.swaggerUrl = `${config.swaggerUI}${config.swaggerPort}`; config.swaggerUrl = `${config.swaggerUI}${config.swaggerPort}`;

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; 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 currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const shipments = await db.shipments.create( const announcements = await db.announcements.create(
{ {
id: data.id || undefined, id: data.id || undefined,
carrier: data.carrier title: data.title
|| ||
null null
, ,
tracking_number: data.tracking_number content: data.content
|| ||
null null
, ,
shipped_at: data.shipped_at published_at: data.published_at
|| ||
null null
, ,
delivered_at: data.delivered_at is_active: data.is_active
|| ||
null false
,
status: data.status
||
null
, ,
importHash: data.importHash || null, 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, 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; const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method // 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, id: item.id || undefined,
carrier: item.carrier title: item.title
|| ||
null null
, ,
tracking_number: item.tracking_number content: item.content
|| ||
null null
, ,
shipped_at: item.shipped_at published_at: item.published_at
|| ||
null null
, ,
delivered_at: item.delivered_at is_active: item.is_active
|| ||
null false
,
status: item.status
||
null
, ,
importHash: item.importHash || null, importHash: item.importHash || null,
@ -107,12 +99,12 @@ module.exports = class ShipmentsDBApi {
})); }));
// Bulk create items // 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 // For each item created, replace relation files
return shipments; return announcements;
} }
static async update(id, data, options) { static async update(id, data, options) {
@ -120,38 +112,35 @@ module.exports = class ShipmentsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const shipments = await db.shipments.findByPk(id, {}, {transaction}); const announcements = await db.announcements.findByPk(id, {}, {transaction});
const updatePayload = {}; 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.is_active !== undefined) updatePayload.is_active = data.is_active;
if (data.status !== undefined) updatePayload.status = data.status;
updatePayload.updatedById = currentUser.id; updatePayload.updatedById = currentUser.id;
await shipments.update(updatePayload, {transaction}); await announcements.update(updatePayload, {transaction});
if (data.order !== undefined) { if (data.course !== undefined) {
await shipments.setOrder( await announcements.setCourse(
data.order, data.course,
{ transaction } { transaction }
); );
@ -163,14 +152,14 @@ module.exports = class ShipmentsDBApi {
return shipments; return announcements;
} }
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const shipments = await db.shipments.findAll({ const announcements = await db.announcements.findAll({
where: { where: {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
@ -180,53 +169,53 @@ module.exports = class ShipmentsDBApi {
}); });
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of shipments) { for (const record of announcements) {
await record.update( await record.update(
{deletedBy: currentUser.id}, {deletedBy: currentUser.id},
{transaction} {transaction}
); );
} }
for (const record of shipments) { for (const record of announcements) {
await record.destroy({transaction}); await record.destroy({transaction});
} }
}); });
return shipments; return announcements;
} }
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; 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 deletedBy: currentUser.id
}, { }, {
transaction, transaction,
}); });
await shipments.destroy({ await announcements.destroy({
transaction transaction
}); });
return shipments; return announcements;
} }
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const shipments = await db.shipments.findOne( const announcements = await db.announcements.findOne(
{ where }, { where },
{ transaction }, { transaction },
); );
if (!shipments) { if (!announcements) {
return shipments; 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 transaction
}); });
@ -271,15 +262,15 @@ module.exports = class ShipmentsDBApi {
let include = [ let include = [
{ {
model: db.orders, model: db.courses,
as: 'order', as: 'course',
where: filter.order ? { where: filter.course ? {
[Op.or]: [ [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: { title: {
[Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) [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 = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'shipments', 'announcements',
'carrier', 'title',
filter.carrier, filter.title,
), ),
}; };
} }
if (filter.tracking_number) { if (filter.content) {
where = { where = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'shipments', 'announcements',
'tracking_number', 'content',
filter.tracking_number, 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.published_atRange) {
if (filter.shipped_atRange) { const [start, end] = filter.published_atRange;
const [start, end] = filter.shipped_atRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
shipped_at: { published_at: {
...where.shipped_at, ...where.published_at,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -361,32 +334,8 @@ module.exports = class ShipmentsDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
shipped_at: { published_at: {
...where.shipped_at, ...where.published_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,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
@ -402,10 +351,10 @@ module.exports = class ShipmentsDBApi {
} }
if (filter.status) { if (filter.is_active) {
where = { where = {
...where, ...where,
status: filter.status, is_active: filter.is_active,
}; };
} }
@ -460,7 +409,7 @@ module.exports = class ShipmentsDBApi {
} }
try { try {
const { rows, count } = await db.shipments.findAndCountAll(queryOptions); const { rows, count } = await db.announcements.findAndCountAll(queryOptions);
return { return {
rows: options?.countOnly ? [] : rows, rows: options?.countOnly ? [] : rows,
@ -482,25 +431,25 @@ module.exports = class ShipmentsDBApi {
[Op.or]: [ [Op.or]: [
{ ['id']: Utils.uuid(query) }, { ['id']: Utils.uuid(query) },
Utils.ilike( Utils.ilike(
'shipments', 'announcements',
'tracking_number', 'title',
query, query,
), ),
], ],
}; };
} }
const records = await db.shipments.findAll({ const records = await db.announcements.findAll({
attributes: [ 'id', 'tracking_number' ], attributes: [ 'id', 'title' ],
where, where,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined, offset: offset ? Number(offset) : undefined,
orderBy: [['tracking_number', 'ASC']], orderBy: [['title', 'ASC']],
}); });
return records.map((record) => ({ return records.map((record) => ({
id: record.id, id: record.id,
label: record.tracking_number, label: record.title,
})); }));
} }

View File

@ -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,
}));
}
};

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; 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 currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const categories = await db.categories.create( const course_categories = await db.course_categories.create(
{ {
id: data.id || undefined, id: data.id || undefined,
@ -26,6 +26,11 @@ module.exports = class CategoriesDBApi {
null null
, ,
slug: data.slug
||
null
,
description: data.description description: data.description
|| ||
null null
@ -39,16 +44,12 @@ module.exports = class CategoriesDBApi {
); );
await categories.setParent( data.parent || null, {
transaction,
});
return course_categories;
return categories;
} }
@ -57,12 +58,17 @@ module.exports = class CategoriesDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method // 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, id: item.id || undefined,
name: item.name name: item.name
|| ||
null null
,
slug: item.slug
||
null
, ,
description: item.description description: item.description
@ -77,12 +83,12 @@ module.exports = class CategoriesDBApi {
})); }));
// Bulk create items // 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 // For each item created, replace relation files
return categories; return course_categories;
} }
static async update(id, data, options) { static async update(id, data, options) {
@ -90,7 +96,7 @@ module.exports = class CategoriesDBApi {
const transaction = (options && options.transaction) || undefined; 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,23 +106,15 @@ module.exports = class CategoriesDBApi {
if (data.name !== undefined) updatePayload.name = data.name; if (data.name !== undefined) updatePayload.name = data.name;
if (data.slug !== undefined) updatePayload.slug = data.slug;
if (data.description !== undefined) updatePayload.description = data.description; if (data.description !== undefined) updatePayload.description = data.description;
updatePayload.updatedById = currentUser.id; 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 }
);
}
@ -124,14 +122,16 @@ module.exports = class CategoriesDBApi {
return categories;
return course_categories;
} }
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const categories = await db.categories.findAll({ const course_categories = await db.course_categories.findAll({
where: { where: {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
@ -141,59 +141,60 @@ module.exports = class CategoriesDBApi {
}); });
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of categories) { for (const record of course_categories) {
await record.update( await record.update(
{deletedBy: currentUser.id}, {deletedBy: currentUser.id},
{transaction} {transaction}
); );
} }
for (const record of categories) { for (const record of course_categories) {
await record.destroy({transaction}); await record.destroy({transaction});
} }
}); });
return categories; return course_categories;
} }
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; 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 deletedBy: currentUser.id
}, { }, {
transaction, transaction,
}); });
await categories.destroy({ await course_categories.destroy({
transaction transaction
}); });
return categories; return course_categories;
} }
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const categories = await db.categories.findOne( const course_categories = await db.course_categories.findOne(
{ where }, { where },
{ transaction }, { transaction },
); );
if (!categories) { if (!course_categories) {
return 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 transaction
}); });
@ -205,10 +206,6 @@ module.exports = class CategoriesDBApi {
output.parent = await categories.getParent({
transaction
});
return output; return output;
@ -235,23 +232,6 @@ module.exports = class CategoriesDBApi {
let include = [ 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 = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'categories', 'course_categories',
'name', 'name',
filter.name, filter.name,
), ),
}; };
} }
if (filter.slug) {
where = {
...where,
[Op.and]: Utils.ilike(
'course_categories',
'slug',
filter.slug,
),
};
}
if (filter.description) { if (filter.description) {
where = { where = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'categories', 'course_categories',
'description', 'description',
filter.description, filter.description,
), ),
@ -305,8 +296,6 @@ module.exports = class CategoriesDBApi {
if (filter.createdAtRange) { if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange; const [start, end] = filter.createdAtRange;
@ -352,7 +341,7 @@ module.exports = class CategoriesDBApi {
} }
try { try {
const { rows, count } = await db.categories.findAndCountAll(queryOptions); const { rows, count } = await db.course_categories.findAndCountAll(queryOptions);
return { return {
rows: options?.countOnly ? [] : rows, rows: options?.countOnly ? [] : rows,
@ -374,7 +363,7 @@ module.exports = class CategoriesDBApi {
[Op.or]: [ [Op.or]: [
{ ['id']: Utils.uuid(query) }, { ['id']: Utils.uuid(query) },
Utils.ilike( Utils.ilike(
'categories', 'course_categories',
'name', 'name',
query, 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' ], attributes: [ 'id', 'name' ],
where, where,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,

View File

@ -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,
}));
}
};

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; 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 currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const products = await db.products.create( const enrollments = await db.enrollments.create(
{ {
id: data.id || undefined, id: data.id || undefined,
sku: data.sku enrollment_code: data.enrollment_code
|| ||
null null
, ,
name: data.name enrolled_at: data.enrolled_at
||
null
,
description: data.description
||
null
,
price: data.price
||
null
,
stock: data.stock
|| ||
null null
, ,
status: data.status status: data.status
|| ||
null
,
progress_percent: data.progress_percent
||
null 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, transaction,
}); });
@ -69,16 +63,16 @@ module.exports = class ProductsDBApi {
await FileDBApi.replaceRelationFiles( await FileDBApi.replaceRelationFiles(
{ {
belongsTo: db.products.getTableName(), belongsTo: db.enrollments.getTableName(),
belongsToColumn: 'images', belongsToColumn: 'certificate',
belongsToId: products.id, belongsToId: enrollments.id,
}, },
data.images, data.certificate,
options, options,
); );
return products; return enrollments;
} }
@ -87,30 +81,15 @@ module.exports = class ProductsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method // 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, id: item.id || undefined,
sku: item.sku enrollment_code: item.enrollment_code
|| ||
null null
, ,
name: item.name enrolled_at: item.enrolled_at
||
null
,
description: item.description
||
null
,
price: item.price
||
null
,
stock: item.stock
|| ||
null null
, ,
@ -118,6 +97,11 @@ module.exports = class ProductsDBApi {
status: item.status status: item.status
|| ||
null null
,
progress_percent: item.progress_percent
||
null
, ,
importHash: item.importHash || null, importHash: item.importHash || null,
@ -127,24 +111,24 @@ module.exports = class ProductsDBApi {
})); }));
// Bulk create items // 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 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( await FileDBApi.replaceRelationFiles(
{ {
belongsTo: db.products.getTableName(), belongsTo: db.enrollments.getTableName(),
belongsToColumn: 'images', belongsToColumn: 'certificate',
belongsToId: products[i].id, belongsToId: enrollments[i].id,
}, },
data[i].images, data[i].certificate,
options, options,
); );
} }
return products; return enrollments;
} }
static async update(id, data, options) { static async update(id, data, options) {
@ -152,41 +136,44 @@ module.exports = class ProductsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const products = await db.products.findByPk(id, {}, {transaction}); const enrollments = await db.enrollments.findByPk(id, {}, {transaction});
const updatePayload = {}; 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.enrolled_at !== undefined) updatePayload.enrolled_at = data.enrolled_at;
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.status !== undefined) updatePayload.status = data.status; if (data.status !== undefined) updatePayload.status = data.status;
if (data.progress_percent !== undefined) updatePayload.progress_percent = data.progress_percent;
updatePayload.updatedById = currentUser.id; updatePayload.updatedById = currentUser.id;
await products.update(updatePayload, {transaction}); await enrollments.update(updatePayload, {transaction});
if (data.category !== undefined) { if (data.student !== undefined) {
await products.setCategory( await enrollments.setStudent(
data.category, data.student,
{ transaction }
);
}
if (data.course !== undefined) {
await enrollments.setCourse(
data.course,
{ transaction } { transaction }
); );
@ -199,23 +186,23 @@ module.exports = class ProductsDBApi {
await FileDBApi.replaceRelationFiles( await FileDBApi.replaceRelationFiles(
{ {
belongsTo: db.products.getTableName(), belongsTo: db.enrollments.getTableName(),
belongsToColumn: 'images', belongsToColumn: 'certificate',
belongsToId: products.id, belongsToId: enrollments.id,
}, },
data.images, data.certificate,
options, options,
); );
return products; return enrollments;
} }
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const products = await db.products.findAll({ const enrollments = await db.enrollments.findAll({
where: { where: {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
@ -225,53 +212,53 @@ module.exports = class ProductsDBApi {
}); });
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of products) { for (const record of enrollments) {
await record.update( await record.update(
{deletedBy: currentUser.id}, {deletedBy: currentUser.id},
{transaction} {transaction}
); );
} }
for (const record of products) { for (const record of enrollments) {
await record.destroy({transaction}); await record.destroy({transaction});
} }
}); });
return products; return enrollments;
} }
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; 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 deletedBy: currentUser.id
}, { }, {
transaction, transaction,
}); });
await products.destroy({ await enrollments.destroy({
transaction transaction
}); });
return products; return enrollments;
} }
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const products = await db.products.findOne( const enrollments = await db.enrollments.findOne(
{ where }, { where },
{ transaction }, { transaction },
); );
if (!products) { if (!enrollments) {
return products; 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 transaction
}); });
output.course = await enrollments.getCourse({
output.images = await products.getImages({
transaction transaction
}); });
output.category = await products.getCategory({ output.certificate = await enrollments.getCertificate({
transaction transaction
}); });
@ -325,15 +315,32 @@ module.exports = class ProductsDBApi {
let include = [ let include = [
{ {
model: db.categories, model: db.users,
as: 'category', as: 'student',
where: filter.category ? { where: filter.student ? {
[Op.or]: [ [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: { firstName: {
[Op.or]: filter.category.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) [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, model: db.file,
as: 'images', as: 'certificate',
}, },
]; ];
@ -359,35 +366,13 @@ module.exports = class ProductsDBApi {
} }
if (filter.sku) { if (filter.enrollment_code) {
where = { where = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'products', 'enrollments',
'sku', 'enrollment_code',
filter.sku, filter.enrollment_code,
),
};
}
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,
), ),
}; };
} }
@ -397,14 +382,14 @@ module.exports = class ProductsDBApi {
if (filter.priceRange) { if (filter.enrolled_atRange) {
const [start, end] = filter.priceRange; const [start, end] = filter.enrolled_atRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
price: { enrolled_at: {
...where.price, ...where.enrolled_at,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -413,22 +398,22 @@ module.exports = class ProductsDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
price: { enrolled_at: {
...where.price, ...where.enrolled_at,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
} }
} }
if (filter.stockRange) { if (filter.progress_percentRange) {
const [start, end] = filter.stockRange; const [start, end] = filter.progress_percentRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
stock: { progress_percent: {
...where.stock, ...where.progress_percent,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -437,8 +422,8 @@ module.exports = class ProductsDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
stock: { progress_percent: {
...where.stock, ...where.progress_percent,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
@ -467,6 +452,8 @@ module.exports = class ProductsDBApi {
if (filter.createdAtRange) { if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange; const [start, end] = filter.createdAtRange;
@ -512,7 +499,7 @@ module.exports = class ProductsDBApi {
} }
try { try {
const { rows, count } = await db.products.findAndCountAll(queryOptions); const { rows, count } = await db.enrollments.findAndCountAll(queryOptions);
return { return {
rows: options?.countOnly ? [] : rows, rows: options?.countOnly ? [] : rows,
@ -534,25 +521,25 @@ module.exports = class ProductsDBApi {
[Op.or]: [ [Op.or]: [
{ ['id']: Utils.uuid(query) }, { ['id']: Utils.uuid(query) },
Utils.ilike( Utils.ilike(
'products', 'enrollments',
'name', 'enrollment_code',
query, query,
), ),
], ],
}; };
} }
const records = await db.products.findAll({ const records = await db.enrollments.findAll({
attributes: [ 'id', 'name' ], attributes: [ 'id', 'enrollment_code' ],
where, where,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined, offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']], orderBy: [['enrollment_code', 'ASC']],
}); });
return records.map((record) => ({ return records.map((record) => ({
id: record.id, id: record.id,
label: record.name, label: record.enrollment_code,
})); }));
} }

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; 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 currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const orders = await db.orders.create( const lessons = await db.lessons.create(
{ {
id: data.id || undefined, id: data.id || undefined,
order_number: data.order_number title: data.title
|| ||
null null
, ,
status: data.status content: data.content
|| ||
null null
, ,
total: data.total order: data.order
|| ||
null null
, ,
placed_at: data.placed_at duration: data.duration
|| ||
null null
, ,
shipped_at: data.shipped_at start_at: data.start_at
|| ||
null null
, ,
delivery_date: data.delivery_date end_at: data.end_at
|| ||
null null
, ,
payment_status: data.payment_status is_published: data.is_published
|| ||
null false
,
shipping_address: data.shipping_address
||
null
, ,
importHash: data.importHash || null, 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, 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; const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method // 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, id: item.id || undefined,
order_number: item.order_number title: item.title
|| ||
null null
, ,
status: item.status content: item.content
|| ||
null null
, ,
total: item.total order: item.order
|| ||
null null
, ,
placed_at: item.placed_at duration: item.duration
|| ||
null null
, ,
shipped_at: item.shipped_at start_at: item.start_at
|| ||
null null
, ,
delivery_date: item.delivery_date end_at: item.end_at
|| ||
null null
, ,
payment_status: item.payment_status is_published: item.is_published
|| ||
null false
,
shipping_address: item.shipping_address
||
null
, ,
importHash: item.importHash || null, importHash: item.importHash || null,
@ -137,12 +139,24 @@ module.exports = class OrdersDBApi {
})); }));
// Bulk create items // 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 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) { static async update(id, data, options) {
@ -150,47 +164,44 @@ module.exports = class OrdersDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const orders = await db.orders.findByPk(id, {}, {transaction}); const lessons = await db.lessons.findByPk(id, {}, {transaction});
const updatePayload = {}; 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.is_published !== undefined) updatePayload.is_published = data.is_published;
if (data.shipping_address !== undefined) updatePayload.shipping_address = data.shipping_address;
updatePayload.updatedById = currentUser.id; updatePayload.updatedById = currentUser.id;
await orders.update(updatePayload, {transaction}); await lessons.update(updatePayload, {transaction});
if (data.customer !== undefined) { if (data.course !== undefined) {
await orders.setCustomer( await lessons.setCourse(
data.customer, data.course,
{ transaction } { 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) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const orders = await db.orders.findAll({ const lessons = await db.lessons.findAll({
where: { where: {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
@ -219,53 +240,53 @@ module.exports = class OrdersDBApi {
}); });
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of orders) { for (const record of lessons) {
await record.update( await record.update(
{deletedBy: currentUser.id}, {deletedBy: currentUser.id},
{transaction} {transaction}
); );
} }
for (const record of orders) { for (const record of lessons) {
await record.destroy({transaction}); await record.destroy({transaction});
} }
}); });
return orders; return lessons;
} }
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; 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 deletedBy: currentUser.id
}, { }, {
transaction, transaction,
}); });
await orders.destroy({ await lessons.destroy({
transaction transaction
}); });
return orders; return lessons;
} }
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const orders = await db.orders.findOne( const lessons = await db.lessons.findOne(
{ where }, { where },
{ transaction }, { transaction },
); );
if (!orders) { if (!lessons) {
return orders; 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 transaction
}); });
output.payments_order = await orders.getPayments_order({ output.quizzes_lesson = await lessons.getQuizzes_lesson({
transaction
});
output.shipments_order = await orders.getShipments_order({
transaction transaction
}); });
output.customer = await orders.getCustomer({
output.course = await lessons.getCourse({
transaction
});
output.video_files = await lessons.getVideo_files({
transaction transaction
}); });
@ -322,15 +346,15 @@ module.exports = class OrdersDBApi {
let include = [ let include = [
{ {
model: db.customers, model: db.courses,
as: 'customer', as: 'course',
where: filter.customer ? { where: filter.course ? {
[Op.or]: [ [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: { title: {
[Op.or]: filter.customer.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) [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) { if (filter) {
@ -351,24 +380,24 @@ module.exports = class OrdersDBApi {
} }
if (filter.order_number) { if (filter.title) {
where = { where = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'orders', 'lessons',
'order_number', 'title',
filter.order_number, filter.title,
), ),
}; };
} }
if (filter.shipping_address) { if (filter.content) {
where = { where = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'orders', 'lessons',
'shipping_address', 'content',
filter.shipping_address, 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 !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
total: { order: {
...where.total, ...where.order,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -394,22 +441,22 @@ module.exports = class OrdersDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
total: { order: {
...where.total, ...where.order,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
} }
} }
if (filter.placed_atRange) { if (filter.durationRange) {
const [start, end] = filter.placed_atRange; const [start, end] = filter.durationRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
placed_at: { duration: {
...where.placed_at, ...where.duration,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -418,22 +465,22 @@ module.exports = class OrdersDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
placed_at: { duration: {
...where.placed_at, ...where.duration,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
} }
} }
if (filter.shipped_atRange) { if (filter.start_atRange) {
const [start, end] = filter.shipped_atRange; const [start, end] = filter.start_atRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
shipped_at: { start_at: {
...where.shipped_at, ...where.start_at,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -442,22 +489,22 @@ module.exports = class OrdersDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
shipped_at: { start_at: {
...where.shipped_at, ...where.start_at,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
} }
} }
if (filter.delivery_dateRange) { if (filter.end_atRange) {
const [start, end] = filter.delivery_dateRange; const [start, end] = filter.end_atRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
delivery_date: { end_at: {
...where.delivery_date, ...where.end_at,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -466,8 +513,8 @@ module.exports = class OrdersDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
delivery_date: { end_at: {
...where.delivery_date, ...where.end_at,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
@ -483,17 +530,10 @@ module.exports = class OrdersDBApi {
} }
if (filter.status) { if (filter.is_published) {
where = { where = {
...where, ...where,
status: filter.status, is_published: filter.is_published,
};
}
if (filter.payment_status) {
where = {
...where,
payment_status: filter.payment_status,
}; };
} }
@ -548,7 +588,7 @@ module.exports = class OrdersDBApi {
} }
try { try {
const { rows, count } = await db.orders.findAndCountAll(queryOptions); const { rows, count } = await db.lessons.findAndCountAll(queryOptions);
return { return {
rows: options?.countOnly ? [] : rows, rows: options?.countOnly ? [] : rows,
@ -570,25 +610,25 @@ module.exports = class OrdersDBApi {
[Op.or]: [ [Op.or]: [
{ ['id']: Utils.uuid(query) }, { ['id']: Utils.uuid(query) },
Utils.ilike( Utils.ilike(
'orders', 'lessons',
'order_number', 'title',
query, query,
), ),
], ],
}; };
} }
const records = await db.orders.findAll({ const records = await db.lessons.findAll({
attributes: [ 'id', 'order_number' ], attributes: [ 'id', 'title' ],
where, where,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined, offset: offset ? Number(offset) : undefined,
orderBy: [['order_number', 'ASC']], orderBy: [['title', 'ASC']],
}); });
return records.map((record) => ({ return records.map((record) => ({
id: record.id, id: record.id,
label: record.order_number, label: record.title,
})); }));
} }

View File

@ -176,6 +176,8 @@ module.exports = class PermissionsDBApi {
return output; return output;
} }

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; 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 currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const order_items = await db.order_items.create( const progress = await db.progress.create(
{ {
id: data.id || undefined, id: data.id || undefined,
name: data.name summary: data.summary
|| ||
null null
, ,
quantity: data.quantity completed: data.completed
||
false
,
completed_at: data.completed_at
|| ||
null null
, ,
unit_price: data.unit_price percent: data.percent
|| ||
null null
, ,
total_price: data.total_price notes: data.notes
|| ||
null null
, ,
@ -49,11 +55,11 @@ module.exports = class Order_itemsDBApi {
); );
await order_items.setOrder( data.order || null, { await progress.setStudent( data.student || null, {
transaction, transaction,
}); });
await order_items.setProduct( data.product || null, { await progress.setLesson( data.lesson || null, {
transaction, 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; const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method // 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, id: item.id || undefined,
name: item.name summary: item.summary
|| ||
null null
, ,
quantity: item.quantity completed: item.completed
||
false
,
completed_at: item.completed_at
|| ||
null null
, ,
unit_price: item.unit_price percent: item.percent
|| ||
null null
, ,
total_price: item.total_price notes: item.notes
|| ||
null null
, ,
@ -101,12 +113,12 @@ module.exports = class Order_itemsDBApi {
})); }));
// Bulk create items // 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 // For each item created, replace relation files
return order_items; return progress;
} }
static async update(id, data, options) { static async update(id, data, options) {
@ -114,44 +126,47 @@ module.exports = class Order_itemsDBApi {
const transaction = (options && options.transaction) || undefined; 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 = {}; 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; updatePayload.updatedById = currentUser.id;
await order_items.update(updatePayload, {transaction}); await progress.update(updatePayload, {transaction});
if (data.order !== undefined) { if (data.student !== undefined) {
await order_items.setOrder( await progress.setStudent(
data.order, data.student,
{ transaction } { transaction }
); );
} }
if (data.product !== undefined) { if (data.lesson !== undefined) {
await order_items.setProduct( await progress.setLesson(
data.product, data.lesson,
{ transaction } { transaction }
); );
@ -163,14 +178,14 @@ module.exports = class Order_itemsDBApi {
return order_items; return progress;
} }
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const order_items = await db.order_items.findAll({ const progress = await db.progress.findAll({
where: { where: {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
@ -180,53 +195,53 @@ module.exports = class Order_itemsDBApi {
}); });
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of order_items) { for (const record of progress) {
await record.update( await record.update(
{deletedBy: currentUser.id}, {deletedBy: currentUser.id},
{transaction} {transaction}
); );
} }
for (const record of order_items) { for (const record of progress) {
await record.destroy({transaction}); await record.destroy({transaction});
} }
}); });
return order_items; return progress;
} }
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; 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 deletedBy: currentUser.id
}, { }, {
transaction, transaction,
}); });
await order_items.destroy({ await progress.destroy({
transaction transaction
}); });
return order_items; return progress;
} }
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const order_items = await db.order_items.findOne( const progress = await db.progress.findOne(
{ where }, { where },
{ transaction }, { transaction },
); );
if (!order_items) { if (!progress) {
return order_items; 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 transaction
}); });
output.product = await order_items.getProduct({ output.lesson = await progress.getLesson({
transaction transaction
}); });
@ -276,15 +293,15 @@ module.exports = class Order_itemsDBApi {
let include = [ let include = [
{ {
model: db.orders, model: db.users,
as: 'order', as: 'student',
where: filter.order ? { where: filter.student ? {
[Op.or]: [ [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: { firstName: {
[Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) [Op.or]: filter.student.split('|').map(term => ({ [Op.iLike]: `%${term}%` }))
} }
}, },
] ]
@ -293,15 +310,15 @@ module.exports = class Order_itemsDBApi {
}, },
{ {
model: db.products, model: db.lessons,
as: 'product', as: 'lesson',
where: filter.product ? { where: filter.lesson ? {
[Op.or]: [ [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: { title: {
[Op.or]: filter.product.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) [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 = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'order_items', 'progress',
'name', 'summary',
filter.name, 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) { if (filter.completed_atRange) {
const [start, end] = filter.quantityRange; const [start, end] = filter.completed_atRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
quantity: { completed_at: {
...where.quantity, ...where.completed_at,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -354,22 +382,22 @@ module.exports = class Order_itemsDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
quantity: { completed_at: {
...where.quantity, ...where.completed_at,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
} }
} }
if (filter.unit_priceRange) { if (filter.percentRange) {
const [start, end] = filter.unit_priceRange; const [start, end] = filter.percentRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
unit_price: { percent: {
...where.unit_price, ...where.percent,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -378,32 +406,8 @@ module.exports = class Order_itemsDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
unit_price: { percent: {
...where.unit_price, ...where.percent,
[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,
[Op.lte]: end, [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 { try {
const { rows, count } = await db.order_items.findAndCountAll(queryOptions); const { rows, count } = await db.progress.findAndCountAll(queryOptions);
return { return {
rows: options?.countOnly ? [] : rows, rows: options?.countOnly ? [] : rows,
@ -494,25 +505,25 @@ module.exports = class Order_itemsDBApi {
[Op.or]: [ [Op.or]: [
{ ['id']: Utils.uuid(query) }, { ['id']: Utils.uuid(query) },
Utils.ilike( Utils.ilike(
'order_items', 'progress',
'name', 'summary',
query, query,
), ),
], ],
}; };
} }
const records = await db.order_items.findAll({ const records = await db.progress.findAll({
attributes: [ 'id', 'name' ], attributes: [ 'id', 'summary' ],
where, where,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined, offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']], orderBy: [['summary', 'ASC']],
}); });
return records.map((record) => ({ return records.map((record) => ({
id: record.id, id: record.id,
label: record.name, label: record.summary,
})); }));
} }

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; 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 currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const customers = await db.customers.create( const quiz_questions = await db.quiz_questions.create(
{ {
id: data.id || undefined, id: data.id || undefined,
name: data.name question: data.question
|| ||
null null
, ,
email: data.email question_type: data.question_type
|| ||
null null
, ,
phone: data.phone choices: data.choices
|| ||
null null
, ,
address: data.address answer: data.answer
||
null
,
notes: data.notes
||
null
,
vip: data.vip
||
false
,
tax_number: data.tax_number
|| ||
null 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; const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method // 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, id: item.id || undefined,
name: item.name question: item.question
|| ||
null null
, ,
email: item.email question_type: item.question_type
|| ||
null null
, ,
phone: item.phone choices: item.choices
|| ||
null null
, ,
address: item.address answer: item.answer
||
null
,
notes: item.notes
||
null
,
vip: item.vip
||
false
,
tax_number: item.tax_number
|| ||
null null
, ,
@ -125,12 +97,12 @@ module.exports = class CustomersDBApi {
})); }));
// Bulk create items // 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 // For each item created, replace relation files
return customers; return quiz_questions;
} }
static async update(id, data, options) { static async update(id, data, options) {
@ -138,37 +110,39 @@ module.exports = class CustomersDBApi {
const transaction = (options && options.transaction) || undefined; 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 = {}; 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.answer !== undefined) updatePayload.answer = data.answer;
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;
updatePayload.updatedById = currentUser.id; 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 }
);
}
@ -176,16 +150,14 @@ module.exports = class CustomersDBApi {
return quiz_questions;
return customers;
} }
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const customers = await db.customers.findAll({ const quiz_questions = await db.quiz_questions.findAll({
where: { where: {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
@ -195,53 +167,53 @@ module.exports = class CustomersDBApi {
}); });
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of customers) { for (const record of quiz_questions) {
await record.update( await record.update(
{deletedBy: currentUser.id}, {deletedBy: currentUser.id},
{transaction} {transaction}
); );
} }
for (const record of customers) { for (const record of quiz_questions) {
await record.destroy({transaction}); await record.destroy({transaction});
} }
}); });
return customers; return quiz_questions;
} }
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; 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 deletedBy: currentUser.id
}, { }, {
transaction, transaction,
}); });
await customers.destroy({ await quiz_questions.destroy({
transaction transaction
}); });
return customers; return quiz_questions;
} }
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const customers = await db.customers.findOne( const quiz_questions = await db.quiz_questions.findOne(
{ where }, { where },
{ transaction }, { transaction },
); );
if (!customers) { if (!quiz_questions) {
return customers; return quiz_questions;
} }
const output = customers.get({plain: true}); const output = quiz_questions.get({plain: true});
@ -250,16 +222,19 @@ module.exports = class CustomersDBApi {
output.orders_customer = await customers.getOrders_customer({
output.quiz = await quiz_questions.getQuiz({
transaction transaction
}); });
return output; return output;
} }
@ -284,6 +259,23 @@ module.exports = class CustomersDBApi {
let include = [ 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 = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'customers', 'quiz_questions',
'name', 'question',
filter.name, filter.question,
), ),
}; };
} }
if (filter.email) { if (filter.choices) {
where = { where = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'customers', 'quiz_questions',
'email', 'choices',
filter.email, filter.choices,
), ),
}; };
} }
if (filter.phone) { if (filter.answer) {
where = { where = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'customers', 'quiz_questions',
'phone', 'answer',
filter.phone, filter.answer,
),
};
}
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,
), ),
}; };
} }
@ -377,10 +336,10 @@ module.exports = class CustomersDBApi {
} }
if (filter.vip) { if (filter.question_type) {
where = { where = {
...where, ...where,
vip: filter.vip, question_type: filter.question_type,
}; };
} }
@ -388,6 +347,8 @@ module.exports = class CustomersDBApi {
if (filter.createdAtRange) { if (filter.createdAtRange) {
const [start, end] = filter.createdAtRange; const [start, end] = filter.createdAtRange;
@ -433,7 +394,7 @@ module.exports = class CustomersDBApi {
} }
try { try {
const { rows, count } = await db.customers.findAndCountAll(queryOptions); const { rows, count } = await db.quiz_questions.findAndCountAll(queryOptions);
return { return {
rows: options?.countOnly ? [] : rows, rows: options?.countOnly ? [] : rows,
@ -455,25 +416,25 @@ module.exports = class CustomersDBApi {
[Op.or]: [ [Op.or]: [
{ ['id']: Utils.uuid(query) }, { ['id']: Utils.uuid(query) },
Utils.ilike( Utils.ilike(
'customers', 'quiz_questions',
'name', 'question',
query, query,
), ),
], ],
}; };
} }
const records = await db.customers.findAll({ const records = await db.quiz_questions.findAll({
attributes: [ 'id', 'name' ], attributes: [ 'id', 'question' ],
where, where,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined, offset: offset ? Number(offset) : undefined,
orderBy: [['name', 'ASC']], orderBy: [['question', 'ASC']],
}); });
return records.map((record) => ({ return records.map((record) => ({
id: record.id, id: record.id,
label: record.name, label: record.question,
})); }));
} }

View File

@ -9,7 +9,7 @@ const Utils = require('../utils');
const Sequelize = db.Sequelize; const Sequelize = db.Sequelize;
const Op = Sequelize.Op; 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 currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const payments = await db.payments.create( const quizzes = await db.quizzes.create(
{ {
id: data.id || undefined, id: data.id || undefined,
reference: data.reference title: data.title
|| ||
null null
, ,
amount: data.amount description: data.description
|| ||
null null
, ,
method: data.method passing_score: data.passing_score
|| ||
null null
, ,
status: data.status time_limit: data.time_limit
|| ||
null null
, ,
paid_at: data.paid_at is_active: data.is_active
|| ||
null false
, ,
importHash: data.importHash || null, 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, 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; const transaction = (options && options.transaction) || undefined;
// Prepare data - wrapping individual data transformations in a map() method // 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, id: item.id || undefined,
reference: item.reference title: item.title
|| ||
null null
, ,
amount: item.amount description: item.description
|| ||
null null
, ,
method: item.method passing_score: item.passing_score
|| ||
null null
, ,
status: item.status time_limit: item.time_limit
|| ||
null null
, ,
paid_at: item.paid_at is_active: item.is_active
|| ||
null false
, ,
importHash: item.importHash || null, importHash: item.importHash || null,
@ -107,12 +109,12 @@ module.exports = class PaymentsDBApi {
})); }));
// Bulk create items // 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 // For each item created, replace relation files
return payments; return quizzes;
} }
static async update(id, data, options) { static async update(id, data, options) {
@ -120,38 +122,38 @@ module.exports = class PaymentsDBApi {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const payments = await db.payments.findByPk(id, {}, {transaction}); const quizzes = await db.quizzes.findByPk(id, {}, {transaction});
const updatePayload = {}; 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; updatePayload.updatedById = currentUser.id;
await payments.update(updatePayload, {transaction}); await quizzes.update(updatePayload, {transaction});
if (data.order !== undefined) { if (data.lesson !== undefined) {
await payments.setOrder( await quizzes.setLesson(
data.order, data.lesson,
{ transaction } { transaction }
); );
@ -163,14 +165,14 @@ module.exports = class PaymentsDBApi {
return payments; return quizzes;
} }
static async deleteByIds(ids, options) { static async deleteByIds(ids, options) {
const currentUser = (options && options.currentUser) || { id: null }; const currentUser = (options && options.currentUser) || { id: null };
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const payments = await db.payments.findAll({ const quizzes = await db.quizzes.findAll({
where: { where: {
id: { id: {
[Op.in]: ids, [Op.in]: ids,
@ -180,53 +182,53 @@ module.exports = class PaymentsDBApi {
}); });
await db.sequelize.transaction(async (transaction) => { await db.sequelize.transaction(async (transaction) => {
for (const record of payments) { for (const record of quizzes) {
await record.update( await record.update(
{deletedBy: currentUser.id}, {deletedBy: currentUser.id},
{transaction} {transaction}
); );
} }
for (const record of payments) { for (const record of quizzes) {
await record.destroy({transaction}); await record.destroy({transaction});
} }
}); });
return payments; return quizzes;
} }
static async remove(id, options) { static async remove(id, options) {
const currentUser = (options && options.currentUser) || {id: null}; const currentUser = (options && options.currentUser) || {id: null};
const transaction = (options && options.transaction) || undefined; 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 deletedBy: currentUser.id
}, { }, {
transaction, transaction,
}); });
await payments.destroy({ await quizzes.destroy({
transaction transaction
}); });
return payments; return quizzes;
} }
static async findBy(where, options) { static async findBy(where, options) {
const transaction = (options && options.transaction) || undefined; const transaction = (options && options.transaction) || undefined;
const payments = await db.payments.findOne( const quizzes = await db.quizzes.findOne(
{ where }, { where },
{ transaction }, { transaction },
); );
if (!payments) { if (!quizzes) {
return payments; 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 transaction
}); });
@ -271,15 +279,15 @@ module.exports = class PaymentsDBApi {
let include = [ let include = [
{ {
model: db.orders, model: db.lessons,
as: 'order', as: 'lesson',
where: filter.order ? { where: filter.lesson ? {
[Op.or]: [ [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: { title: {
[Op.or]: filter.order.split('|').map(term => ({ [Op.iLike]: `%${term}%` })) [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 = {
...where, ...where,
[Op.and]: Utils.ilike( [Op.and]: Utils.ilike(
'payments', 'quizzes',
'reference', 'title',
filter.reference, 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) { if (filter.passing_scoreRange) {
const [start, end] = filter.amountRange; const [start, end] = filter.passing_scoreRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
amount: { passing_score: {
...where.amount, ...where.passing_score,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -332,22 +351,22 @@ module.exports = class PaymentsDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
amount: { passing_score: {
...where.amount, ...where.passing_score,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
} }
} }
if (filter.paid_atRange) { if (filter.time_limitRange) {
const [start, end] = filter.paid_atRange; const [start, end] = filter.time_limitRange;
if (start !== undefined && start !== null && start !== '') { if (start !== undefined && start !== null && start !== '') {
where = { where = {
...where, ...where,
paid_at: { time_limit: {
...where.paid_at, ...where.time_limit,
[Op.gte]: start, [Op.gte]: start,
}, },
}; };
@ -356,8 +375,8 @@ module.exports = class PaymentsDBApi {
if (end !== undefined && end !== null && end !== '') { if (end !== undefined && end !== null && end !== '') {
where = { where = {
...where, ...where,
paid_at: { time_limit: {
...where.paid_at, ...where.time_limit,
[Op.lte]: end, [Op.lte]: end,
}, },
}; };
@ -373,17 +392,10 @@ module.exports = class PaymentsDBApi {
} }
if (filter.method) { if (filter.is_active) {
where = { where = {
...where, ...where,
method: filter.method, is_active: filter.is_active,
};
}
if (filter.status) {
where = {
...where,
status: filter.status,
}; };
} }
@ -438,7 +450,7 @@ module.exports = class PaymentsDBApi {
} }
try { try {
const { rows, count } = await db.payments.findAndCountAll(queryOptions); const { rows, count } = await db.quizzes.findAndCountAll(queryOptions);
return { return {
rows: options?.countOnly ? [] : rows, rows: options?.countOnly ? [] : rows,
@ -460,25 +472,25 @@ module.exports = class PaymentsDBApi {
[Op.or]: [ [Op.or]: [
{ ['id']: Utils.uuid(query) }, { ['id']: Utils.uuid(query) },
Utils.ilike( Utils.ilike(
'payments', 'quizzes',
'reference', 'title',
query, query,
), ),
], ],
}; };
} }
const records = await db.payments.findAll({ const records = await db.quizzes.findAll({
attributes: [ 'id', 'reference' ], attributes: [ 'id', 'title' ],
where, where,
limit: limit ? Number(limit) : undefined, limit: limit ? Number(limit) : undefined,
offset: offset ? Number(offset) : undefined, offset: offset ? Number(offset) : undefined,
orderBy: [['reference', 'ASC']], orderBy: [['title', 'ASC']],
}); });
return records.map((record) => ({ return records.map((record) => ({
id: record.id, id: record.id,
label: record.reference, label: record.title,
})); }));
} }

View File

@ -200,6 +200,8 @@ module.exports = class RolesDBApi {
output.permissions = await roles.getPermissions({ output.permissions = await roles.getPermissions({
transaction transaction
}); });

View File

@ -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({ output.avatar = await users.getAvatar({

View File

@ -15,7 +15,7 @@ module.exports = {
username: 'postgres', username: 'postgres',
dialect: 'postgres', dialect: 'postgres',
password: '', password: '',
database: 'db_store_operations_manager', database: 'db_courseflow_lms',
host: process.env.DB_HOST || 'localhost', host: process.env.DB_HOST || 'localhost',
logging: console.log, logging: console.log,
seederStorage: 'sequelize', seederStorage: 'sequelize',

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment'); const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const payments = sequelize.define( const announcements = sequelize.define(
'payments', 'announcements',
{ {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,69 +14,35 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true, primaryKey: true,
}, },
reference: { title: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
amount: { content: {
type: DataTypes.DECIMAL, type: DataTypes.TEXT,
}, },
method: { published_at: {
type: DataTypes.ENUM,
values: [
"Card",
"PayPal",
"BankTransfer",
"Cash"
],
},
status: {
type: DataTypes.ENUM,
values: [
"Pending",
"Completed",
"Failed",
"Refunded"
],
},
paid_at: {
type: DataTypes.DATE, type: DataTypes.DATE,
},
is_active: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
}, },
importHash: { 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 /// 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 //end loop
db.payments.belongsTo(db.orders, { db.announcements.belongsTo(db.courses, {
as: 'order', as: 'course',
foreignKey: { foreignKey: {
name: 'orderId', name: 'courseId',
}, },
constraints: false, constraints: false,
}); });
@ -124,18 +92,18 @@ paid_at: {
db.payments.belongsTo(db.users, { db.announcements.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });
db.payments.belongsTo(db.users, { db.announcements.belongsTo(db.users, {
as: 'updatedBy', as: 'updatedBy',
}); });
}; };
return payments; return announcements;
}; };

View File

@ -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;
};

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment'); const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const categories = sequelize.define( const course_categories = sequelize.define(
'categories', 'course_categories',
{ {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -19,6 +19,13 @@ name: {
},
slug: {
type: DataTypes.TEXT,
}, },
description: { 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 /// 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: { foreignKey: {
name: 'categoryId', name: 'categoryId',
}, },
@ -66,33 +74,26 @@ description: {
//end loop //end loop
db.categories.belongsTo(db.categories, {
as: 'parent',
foreignKey: {
name: 'parentId',
},
constraints: false,
});
db.course_categories.belongsTo(db.users, {
db.categories.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });
db.categories.belongsTo(db.users, { db.course_categories.belongsTo(db.users, {
as: 'updatedBy', as: 'updatedBy',
}); });
}; };
return categories; return course_categories;
}; };

View File

@ -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;
};

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment'); const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const products = sequelize.define( const enrollments = sequelize.define(
'products', 'enrollments',
{ {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,36 +14,15 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true, primaryKey: true,
}, },
sku: { enrollment_code: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
name: { enrolled_at: {
type: DataTypes.TEXT, type: DataTypes.DATE,
},
description: {
type: DataTypes.TEXT,
},
price: {
type: DataTypes.DECIMAL,
},
stock: {
type: DataTypes.INTEGER,
@ -56,18 +35,28 @@ status: {
values: [ values: [
"pending",
"active", "active",
"inactive", "completed",
"archived" "dropped"
], ],
}, },
progress_percent: {
type: DataTypes.DECIMAL,
},
importHash: { importHash: {
type: DataTypes.STRING(255), type: DataTypes.STRING(255),
allowNull: true, 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 /// 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, { db.enrollments.belongsTo(db.users, {
as: 'category', as: 'student',
foreignKey: { foreignKey: {
name: 'categoryId', name: 'studentId',
},
constraints: false,
});
db.enrollments.belongsTo(db.courses, {
as: 'course',
foreignKey: {
name: 'courseId',
}, },
constraints: false, constraints: false,
}); });
db.products.hasMany(db.file, { db.enrollments.hasMany(db.file, {
as: 'images', as: 'certificate',
foreignKey: 'belongsToId', foreignKey: 'belongsToId',
constraints: false, constraints: false,
scope: { scope: {
belongsTo: db.products.getTableName(), belongsTo: db.enrollments.getTableName(),
belongsToColumn: 'images', belongsToColumn: 'certificate',
}, },
}); });
db.products.belongsTo(db.users, { db.enrollments.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });
db.products.belongsTo(db.users, { db.enrollments.belongsTo(db.users, {
as: 'updatedBy', as: 'updatedBy',
}); });
}; };
return products; return enrollments;
}; };

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment'); const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const orders = sequelize.define( const lessons = sequelize.define(
'orders', 'lessons',
{ {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,99 +14,56 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true, primaryKey: true,
}, },
order_number: { title: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
status: { content: {
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: {
type: DataTypes.TEXT, 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: { 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 /// 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, { db.lessons.hasMany(db.progress, {
as: 'order_items_order', as: 'progress_lesson',
foreignKey: { foreignKey: {
name: 'orderId', name: 'lessonId',
}, },
constraints: false, constraints: false,
}); });
db.orders.hasMany(db.payments, { db.lessons.hasMany(db.quizzes, {
as: 'payments_order', as: 'quizzes_lesson',
foreignKey: { foreignKey: {
name: 'orderId', name: 'lessonId',
}, },
constraints: false, 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, { db.lessons.belongsTo(db.courses, {
as: 'customer', as: 'course',
foreignKey: { foreignKey: {
name: 'customerId', name: 'courseId',
}, },
constraints: false, 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', as: 'createdBy',
}); });
db.orders.belongsTo(db.users, { db.lessons.belongsTo(db.users, {
as: 'updatedBy', as: 'updatedBy',
}); });
}; };
return orders; return lessons;
}; };

View File

@ -51,6 +51,8 @@ name: {
//end loop //end loop

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment'); const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const customers = sequelize.define( const progress = sequelize.define(
'customers', 'progress',
{ {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,42 +14,14 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true, primaryKey: true,
}, },
name: { summary: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
email: { completed: {
type: DataTypes.TEXT,
},
phone: {
type: DataTypes.TEXT,
},
address: {
type: DataTypes.TEXT,
},
notes: {
type: DataTypes.TEXT,
},
vip: {
type: DataTypes.BOOLEAN, type: DataTypes.BOOLEAN,
allowNull: false, allowNull: false,
@ -59,7 +31,21 @@ vip: {
}, },
tax_number: { completed_at: {
type: DataTypes.DATE,
},
percent: {
type: DataTypes.DECIMAL,
},
notes: {
type: DataTypes.TEXT, 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 /// 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', as: 'createdBy',
}); });
db.customers.belongsTo(db.users, { db.progress.belongsTo(db.users, {
as: 'updatedBy', as: 'updatedBy',
}); });
}; };
return customers; return progress;
}; };

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment'); const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const shipments = sequelize.define( const quiz_questions = sequelize.define(
'shipments', 'quiz_questions',
{ {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,56 +14,46 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true, primaryKey: true,
}, },
carrier: { question: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
tracking_number: { question_type: {
type: DataTypes.TEXT,
},
shipped_at: {
type: DataTypes.DATE,
},
delivered_at: {
type: DataTypes.DATE,
},
status: {
type: DataTypes.ENUM, type: DataTypes.ENUM,
values: [ values: [
"Pending", "multiple_choice",
"InTransit", "true_false",
"Delivered", "short_answer"
"Returned"
], ],
}, },
choices: {
type: DataTypes.TEXT,
},
answer: {
type: DataTypes.TEXT,
},
importHash: { importHash: {
type: DataTypes.STRING(255), type: DataTypes.STRING(255),
allowNull: true, 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 /// 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 //end loop
db.shipments.belongsTo(db.orders, { db.quiz_questions.belongsTo(db.quizzes, {
as: 'order', as: 'quiz',
foreignKey: { foreignKey: {
name: 'orderId', name: 'quizId',
}, },
constraints: false, constraints: false,
}); });
@ -109,18 +101,18 @@ status: {
db.shipments.belongsTo(db.users, { db.quiz_questions.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });
db.shipments.belongsTo(db.users, { db.quiz_questions.belongsTo(db.users, {
as: 'updatedBy', as: 'updatedBy',
}); });
}; };
return shipments; return quiz_questions;
}; };

View File

@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
const moment = require('moment'); const moment = require('moment');
module.exports = function(sequelize, DataTypes) { module.exports = function(sequelize, DataTypes) {
const order_items = sequelize.define( const quizzes = sequelize.define(
'order_items', 'quizzes',
{ {
id: { id: {
type: DataTypes.UUID, type: DataTypes.UUID,
@ -14,29 +14,39 @@ module.exports = function(sequelize, DataTypes) {
primaryKey: true, primaryKey: true,
}, },
name: { title: {
type: DataTypes.TEXT, type: DataTypes.TEXT,
}, },
quantity: { description: {
type: DataTypes.TEXT,
},
passing_score: {
type: DataTypes.INTEGER, type: DataTypes.INTEGER,
}, },
unit_price: { time_limit: {
type: DataTypes.DECIMAL, type: DataTypes.INTEGER,
}, },
total_price: { is_active: {
type: DataTypes.DECIMAL, 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 /// 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 //end loop
db.order_items.belongsTo(db.orders, { db.quizzes.belongsTo(db.lessons, {
as: 'order', as: 'lesson',
foreignKey: { foreignKey: {
name: 'orderId', name: 'lessonId',
},
constraints: false,
});
db.order_items.belongsTo(db.products, {
as: 'product',
foreignKey: {
name: 'productId',
}, },
constraints: false, constraints: false,
}); });
@ -95,18 +107,18 @@ total_price: {
db.order_items.belongsTo(db.users, { db.quizzes.belongsTo(db.users, {
as: 'createdBy', as: 'createdBy',
}); });
db.order_items.belongsTo(db.users, { db.quizzes.belongsTo(db.users, {
as: 'updatedBy', as: 'updatedBy',
}); });
}; };
return order_items; return quizzes;
}; };

View File

@ -84,6 +84,8 @@ role_customization: {
//end loop //end loop

View File

@ -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 //end loop

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -26,19 +26,23 @@ const rolesRoutes = require('./routes/roles');
const permissionsRoutes = require('./routes/permissions'); 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) => { const getBaseUrl = (url) => {
@ -51,8 +55,8 @@ const options = {
openapi: "3.0.0", openapi: "3.0.0",
info: { info: {
version: "1.0.0", version: "1.0.0",
title: "Store Operations Manager", title: "CourseFlow LMS",
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.", 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: [ 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/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( app.use(
'/api/openai', '/api/openai',

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const CategoriesService = require('../services/categories'); const AnnouncementsService = require('../services/announcements');
const CategoriesDBApi = require('../db/api/categories'); const AnnouncementsDBApi = require('../db/api/announcements');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
@ -15,23 +15,23 @@ const {
checkCrudPermissions, checkCrudPermissions,
} = require('../middlewares/check-permissions'); } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('categories')); router.use(checkCrudPermissions('announcements'));
/** /**
* @swagger * @swagger
* components: * components:
* schemas: * schemas:
* Categories: * Announcements:
* type: object * type: object
* properties: * properties:
* name: * title:
* type: string * type: string
* default: name * default: title
* description: * content:
* type: string * type: string
* default: description * default: content
@ -40,17 +40,17 @@ router.use(checkCrudPermissions('categories'));
/** /**
* @swagger * @swagger
* tags: * tags:
* name: Categories * name: Announcements
* description: The Categories managing API * description: The Announcements managing API
*/ */
/** /**
* @swagger * @swagger
* /api/categories: * /api/announcements:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Add new item * summary: Add new item
* description: Add new item * description: Add new item
* requestBody: * requestBody:
@ -62,14 +62,14 @@ router.use(checkCrudPermissions('categories'));
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* responses: * responses:
* 200: * 200:
* description: The item was successfully added * description: The item was successfully added
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -80,7 +80,7 @@ router.use(checkCrudPermissions('categories'));
router.post('/', wrapAsync(async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
@ -91,7 +91,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Bulk import items * summary: Bulk import items
* description: Bulk import items * description: Bulk import items
* requestBody: * requestBody:
@ -104,14 +104,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items * description: Data of the updated items
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* responses: * responses:
* 200: * 200:
* description: The items were successfully imported * description: The items were successfully imported
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -123,18 +123,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => { router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); const link = new URL(referer);
await CategoriesService.bulkImport(req, res, true, link.host); await AnnouncementsService.bulkImport(req, res, true, link.host);
const payload = true; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/categories/{id}: * /api/announcements/{id}:
* put: * put:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Update the data of the selected item * summary: Update the data of the selected item
* description: Update the data of the selected item * description: Update the data of the selected item
* parameters: * parameters:
@ -157,7 +157,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* required: * required:
* - id * - id
* responses: * responses:
@ -166,7 +166,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -177,18 +177,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.put('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/categories/{id}: * /api/announcements/{id}:
* delete: * delete:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Delete the selected item * summary: Delete the selected item
* description: Delete the selected item * description: Delete the selected item
* parameters: * parameters:
@ -204,7 +204,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -215,18 +215,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.delete('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/categories/deleteByIds: * /api/announcements/deleteByIds:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Delete the selected item list * summary: Delete the selected item list
* description: Delete the selected item list * description: Delete the selected item list
* requestBody: * requestBody:
@ -244,7 +244,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -253,29 +253,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.post('/deleteByIds', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/categories: * /api/announcements:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Get all categories * summary: Get all announcements
* description: Get all categories * description: Get all announcements
* responses: * responses:
* 200: * 200:
* description: Categories list successfully received * description: Announcements list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -287,14 +287,14 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype const filetype = req.query.filetype
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await CategoriesDBApi.findAll( const payload = await AnnouncementsDBApi.findAll(
req.query, { currentUser } req.query, { currentUser }
); );
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id','name','description', const fields = ['id','title','content',
'published_at',
]; ];
const opts = { fields }; const opts = { fields };
try { try {
@ -313,22 +313,22 @@ router.get('/', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/categories/count: * /api/announcements/count:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Count all categories * summary: Count all announcements
* description: Count all categories * description: Count all announcements
* responses: * responses:
* 200: * 200:
* description: Categories count successfully received * description: Announcements count successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -339,7 +339,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await CategoriesDBApi.findAll( const payload = await AnnouncementsDBApi.findAll(
req.query, req.query,
null, null,
{ countOnly: true, currentUser } { countOnly: true, currentUser }
@ -350,22 +350,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/categories/autocomplete: * /api/announcements/autocomplete:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Find all categories that match search criteria * summary: Find all announcements that match search criteria
* description: Find all categories that match search criteria * description: Find all announcements that match search criteria
* responses: * responses:
* 200: * 200:
* description: Categories list successfully received * description: Announcements list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -375,7 +375,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/ */
router.get('/autocomplete', async (req, res) => { router.get('/autocomplete', async (req, res) => {
const payload = await CategoriesDBApi.findAllAutocomplete( const payload = await AnnouncementsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
@ -387,11 +387,11 @@ router.get('/autocomplete', async (req, res) => {
/** /**
* @swagger * @swagger
* /api/categories/{id}: * /api/announcements/{id}:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Categories] * tags: [Announcements]
* summary: Get selected item * summary: Get selected item
* description: Get selected item * description: Get selected item
* parameters: * parameters:
@ -407,7 +407,7 @@ router.get('/autocomplete', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Categories" * $ref: "#/components/schemas/Announcements"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -418,7 +418,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await CategoriesDBApi.findBy( const payload = await AnnouncementsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
); );

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const ShipmentsService = require('../services/shipments'); const CertificatesService = require('../services/certificates');
const ShipmentsDBApi = require('../db/api/shipments'); const CertificatesDBApi = require('../db/api/certificates');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
@ -15,43 +15,39 @@ const {
checkCrudPermissions, checkCrudPermissions,
} = require('../middlewares/check-permissions'); } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('shipments')); router.use(checkCrudPermissions('certificates'));
/** /**
* @swagger * @swagger
* components: * components:
* schemas: * schemas:
* Shipments: * Certificates:
* type: object * type: object
* properties: * properties:
* carrier: * serial:
* type: string * type: string
* default: carrier * default: serial
* tracking_number:
* type: string
* default: tracking_number
*
*/ */
/** /**
* @swagger * @swagger
* tags: * tags:
* name: Shipments * name: Certificates
* description: The Shipments managing API * description: The Certificates managing API
*/ */
/** /**
* @swagger * @swagger
* /api/shipments: * /api/certificates:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Add new item * summary: Add new item
* description: Add new item * description: Add new item
* requestBody: * requestBody:
@ -63,14 +59,14 @@ router.use(checkCrudPermissions('shipments'));
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* responses: * responses:
* 200: * 200:
* description: The item was successfully added * description: The item was successfully added
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -81,7 +77,7 @@ router.use(checkCrudPermissions('shipments'));
router.post('/', wrapAsync(async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
@ -92,7 +88,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Bulk import items * summary: Bulk import items
* description: Bulk import items * description: Bulk import items
* requestBody: * requestBody:
@ -105,14 +101,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items * description: Data of the updated items
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* responses: * responses:
* 200: * 200:
* description: The items were successfully imported * description: The items were successfully imported
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -124,18 +120,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => { router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); const link = new URL(referer);
await ShipmentsService.bulkImport(req, res, true, link.host); await CertificatesService.bulkImport(req, res, true, link.host);
const payload = true; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/shipments/{id}: * /api/certificates/{id}:
* put: * put:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Update the data of the selected item * summary: Update the data of the selected item
* description: Update the data of the selected item * description: Update the data of the selected item
* parameters: * parameters:
@ -158,7 +154,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* required: * required:
* - id * - id
* responses: * responses:
@ -167,7 +163,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -178,18 +174,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.put('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/shipments/{id}: * /api/certificates/{id}:
* delete: * delete:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Delete the selected item * summary: Delete the selected item
* description: Delete the selected item * description: Delete the selected item
* parameters: * parameters:
@ -205,7 +201,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -216,18 +212,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.delete('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/shipments/deleteByIds: * /api/certificates/deleteByIds:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Delete the selected item list * summary: Delete the selected item list
* description: Delete the selected item list * description: Delete the selected item list
* requestBody: * requestBody:
@ -245,7 +241,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -254,29 +250,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.post('/deleteByIds', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/shipments: * /api/certificates:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Get all shipments * summary: Get all certificates
* description: Get all shipments * description: Get all certificates
* responses: * responses:
* 200: * 200:
* description: Shipments list successfully received * description: Certificates list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -288,14 +284,14 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype const filetype = req.query.filetype
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await ShipmentsDBApi.findAll( const payload = await CertificatesDBApi.findAll(
req.query, { currentUser } req.query, { currentUser }
); );
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id','carrier','tracking_number', const fields = ['id','serial',
'shipped_at','delivered_at', 'issued_at',
]; ];
const opts = { fields }; const opts = { fields };
try { try {
@ -314,22 +310,22 @@ router.get('/', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/shipments/count: * /api/certificates/count:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Count all shipments * summary: Count all certificates
* description: Count all shipments * description: Count all certificates
* responses: * responses:
* 200: * 200:
* description: Shipments count successfully received * description: Certificates count successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -340,7 +336,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await ShipmentsDBApi.findAll( const payload = await CertificatesDBApi.findAll(
req.query, req.query,
null, null,
{ countOnly: true, currentUser } { countOnly: true, currentUser }
@ -351,22 +347,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/shipments/autocomplete: * /api/certificates/autocomplete:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Find all shipments that match search criteria * summary: Find all certificates that match search criteria
* description: Find all shipments that match search criteria * description: Find all certificates that match search criteria
* responses: * responses:
* 200: * 200:
* description: Shipments list successfully received * description: Certificates list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -376,7 +372,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/ */
router.get('/autocomplete', async (req, res) => { router.get('/autocomplete', async (req, res) => {
const payload = await ShipmentsDBApi.findAllAutocomplete( const payload = await CertificatesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
@ -388,11 +384,11 @@ router.get('/autocomplete', async (req, res) => {
/** /**
* @swagger * @swagger
* /api/shipments/{id}: * /api/certificates/{id}:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Shipments] * tags: [Certificates]
* summary: Get selected item * summary: Get selected item
* description: Get selected item * description: Get selected item
* parameters: * parameters:
@ -408,7 +404,7 @@ router.get('/autocomplete', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Shipments" * $ref: "#/components/schemas/Certificates"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -419,7 +415,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ShipmentsDBApi.findBy( const payload = await CertificatesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
); );

View File

@ -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;

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const CustomersService = require('../services/customers'); const CoursesService = require('../services/courses');
const CustomersDBApi = require('../db/api/customers'); const CoursesDBApi = require('../db/api/courses');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
@ -15,54 +15,55 @@ const {
checkCrudPermissions, checkCrudPermissions,
} = require('../middlewares/check-permissions'); } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('customers')); router.use(checkCrudPermissions('courses'));
/** /**
* @swagger * @swagger
* components: * components:
* schemas: * schemas:
* Customers: * Courses:
* type: object * type: object
* properties: * properties:
* name: * title:
* type: string * type: string
* default: name * default: title
* email: * short_description:
* type: string * type: string
* default: email * default: short_description
* phone: * description:
* type: string * type: string
* default: phone * default: description
* address: * language:
* type: string * type: string
* default: address * default: language
* notes:
* type: string
* default: notes
* tax_number:
* type: string
* default: tax_number
* duration:
* type: integer
* format: int64
* price:
* type: integer
* format: int64
*
*/ */
/** /**
* @swagger * @swagger
* tags: * tags:
* name: Customers * name: Courses
* description: The Customers managing API * description: The Courses managing API
*/ */
/** /**
* @swagger * @swagger
* /api/customers: * /api/courses:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Add new item * summary: Add new item
* description: Add new item * description: Add new item
* requestBody: * requestBody:
@ -74,14 +75,14 @@ router.use(checkCrudPermissions('customers'));
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* responses: * responses:
* 200: * 200:
* description: The item was successfully added * description: The item was successfully added
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -92,7 +93,7 @@ router.use(checkCrudPermissions('customers'));
router.post('/', wrapAsync(async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
@ -103,7 +104,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Bulk import items * summary: Bulk import items
* description: Bulk import items * description: Bulk import items
* requestBody: * requestBody:
@ -116,14 +117,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items * description: Data of the updated items
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* responses: * responses:
* 200: * 200:
* description: The items were successfully imported * description: The items were successfully imported
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -135,18 +136,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => { router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); const link = new URL(referer);
await CustomersService.bulkImport(req, res, true, link.host); await CoursesService.bulkImport(req, res, true, link.host);
const payload = true; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/customers/{id}: * /api/courses/{id}:
* put: * put:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Update the data of the selected item * summary: Update the data of the selected item
* description: Update the data of the selected item * description: Update the data of the selected item
* parameters: * parameters:
@ -169,7 +170,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* required: * required:
* - id * - id
* responses: * responses:
@ -178,7 +179,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -189,18 +190,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.put('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/customers/{id}: * /api/courses/{id}:
* delete: * delete:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Delete the selected item * summary: Delete the selected item
* description: Delete the selected item * description: Delete the selected item
* parameters: * parameters:
@ -216,7 +217,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -227,18 +228,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.delete('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/customers/deleteByIds: * /api/courses/deleteByIds:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Delete the selected item list * summary: Delete the selected item list
* description: Delete the selected item list * description: Delete the selected item list
* requestBody: * requestBody:
@ -256,7 +257,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -265,29 +266,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.post('/deleteByIds', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/customers: * /api/courses:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Get all customers * summary: Get all courses
* description: Get all customers * description: Get all courses
* responses: * responses:
* 200: * 200:
* description: Customers list successfully received * description: Courses list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -299,14 +300,14 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype const filetype = req.query.filetype
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await CustomersDBApi.findAll( const payload = await CoursesDBApi.findAll(
req.query, { currentUser } req.query, { currentUser }
); );
if (filetype && filetype === 'csv') { 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 }; const opts = { fields };
try { try {
@ -325,22 +326,22 @@ router.get('/', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/customers/count: * /api/courses/count:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Count all customers * summary: Count all courses
* description: Count all customers * description: Count all courses
* responses: * responses:
* 200: * 200:
* description: Customers count successfully received * description: Courses count successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -351,7 +352,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await CustomersDBApi.findAll( const payload = await CoursesDBApi.findAll(
req.query, req.query,
null, null,
{ countOnly: true, currentUser } { countOnly: true, currentUser }
@ -362,22 +363,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/customers/autocomplete: * /api/courses/autocomplete:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Find all customers that match search criteria * summary: Find all courses that match search criteria
* description: Find all customers that match search criteria * description: Find all courses that match search criteria
* responses: * responses:
* 200: * 200:
* description: Customers list successfully received * description: Courses list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -387,7 +388,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/ */
router.get('/autocomplete', async (req, res) => { router.get('/autocomplete', async (req, res) => {
const payload = await CustomersDBApi.findAllAutocomplete( const payload = await CoursesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
@ -399,11 +400,11 @@ router.get('/autocomplete', async (req, res) => {
/** /**
* @swagger * @swagger
* /api/customers/{id}: * /api/courses/{id}:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Customers] * tags: [Courses]
* summary: Get selected item * summary: Get selected item
* description: Get selected item * description: Get selected item
* parameters: * parameters:
@ -419,7 +420,7 @@ router.get('/autocomplete', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Customers" * $ref: "#/components/schemas/Courses"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -430,7 +431,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await CustomersDBApi.findBy( const payload = await CoursesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
); );

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const Order_itemsService = require('../services/order_items'); const EnrollmentsService = require('../services/enrollments');
const Order_itemsDBApi = require('../db/api/order_items'); const EnrollmentsDBApi = require('../db/api/enrollments');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
@ -15,48 +15,43 @@ const {
checkCrudPermissions, checkCrudPermissions,
} = require('../middlewares/check-permissions'); } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('order_items')); router.use(checkCrudPermissions('enrollments'));
/** /**
* @swagger * @swagger
* components: * components:
* schemas: * schemas:
* Order_items: * Enrollments:
* type: object * type: object
* properties: * properties:
* name: * enrollment_code:
* type: string * type: string
* default: name * default: enrollment_code
* quantity:
* type: integer * progress_percent:
* format: int64
* unit_price:
* type: integer
* format: int64
* total_price:
* type: integer * type: integer
* format: int64 * format: int64
*
*/ */
/** /**
* @swagger * @swagger
* tags: * tags:
* name: Order_items * name: Enrollments
* description: The Order_items managing API * description: The Enrollments managing API
*/ */
/** /**
* @swagger * @swagger
* /api/order_items: * /api/enrollments:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Add new item * summary: Add new item
* description: Add new item * description: Add new item
* requestBody: * requestBody:
@ -68,14 +63,14 @@ router.use(checkCrudPermissions('order_items'));
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* responses: * responses:
* 200: * 200:
* description: The item was successfully added * description: The item was successfully added
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -86,7 +81,7 @@ router.use(checkCrudPermissions('order_items'));
router.post('/', wrapAsync(async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
@ -97,7 +92,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Bulk import items * summary: Bulk import items
* description: Bulk import items * description: Bulk import items
* requestBody: * requestBody:
@ -110,14 +105,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items * description: Data of the updated items
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* responses: * responses:
* 200: * 200:
* description: The items were successfully imported * description: The items were successfully imported
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -129,18 +124,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => { router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/order_items/{id}: * /api/enrollments/{id}:
* put: * put:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Update the data of the selected item * summary: Update the data of the selected item
* description: Update the data of the selected item * description: Update the data of the selected item
* parameters: * parameters:
@ -163,7 +158,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* required: * required:
* - id * - id
* responses: * responses:
@ -172,7 +167,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -183,18 +178,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.put('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/order_items/{id}: * /api/enrollments/{id}:
* delete: * delete:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Delete the selected item * summary: Delete the selected item
* description: Delete the selected item * description: Delete the selected item
* parameters: * parameters:
@ -210,7 +205,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -221,18 +216,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.delete('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/order_items/deleteByIds: * /api/enrollments/deleteByIds:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Delete the selected item list * summary: Delete the selected item list
* description: Delete the selected item list * description: Delete the selected item list
* requestBody: * requestBody:
@ -250,7 +245,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -259,29 +254,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.post('/deleteByIds', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/order_items: * /api/enrollments:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Get all order_items * summary: Get all enrollments
* description: Get all order_items * description: Get all enrollments
* responses: * responses:
* 200: * 200:
* description: Order_items list successfully received * description: Enrollments list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -293,14 +288,14 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype const filetype = req.query.filetype
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await Order_itemsDBApi.findAll( const payload = await EnrollmentsDBApi.findAll(
req.query, { currentUser } req.query, { currentUser }
); );
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id','name', const fields = ['id','enrollment_code',
'quantity',
'unit_price','total_price',
'progress_percent',
'enrolled_at',
]; ];
const opts = { fields }; const opts = { fields };
try { try {
@ -319,22 +314,22 @@ router.get('/', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/order_items/count: * /api/enrollments/count:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Count all order_items * summary: Count all enrollments
* description: Count all order_items * description: Count all enrollments
* responses: * responses:
* 200: * 200:
* description: Order_items count successfully received * description: Enrollments count successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -345,7 +340,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await Order_itemsDBApi.findAll( const payload = await EnrollmentsDBApi.findAll(
req.query, req.query,
null, null,
{ countOnly: true, currentUser } { countOnly: true, currentUser }
@ -356,22 +351,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/order_items/autocomplete: * /api/enrollments/autocomplete:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Find all order_items that match search criteria * summary: Find all enrollments that match search criteria
* description: Find all order_items that match search criteria * description: Find all enrollments that match search criteria
* responses: * responses:
* 200: * 200:
* description: Order_items list successfully received * description: Enrollments list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -381,7 +376,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/ */
router.get('/autocomplete', 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.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
@ -393,11 +388,11 @@ router.get('/autocomplete', async (req, res) => {
/** /**
* @swagger * @swagger
* /api/order_items/{id}: * /api/enrollments/{id}:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Order_items] * tags: [Enrollments]
* summary: Get selected item * summary: Get selected item
* description: Get selected item * description: Get selected item
* parameters: * parameters:
@ -413,7 +408,7 @@ router.get('/autocomplete', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Order_items" * $ref: "#/components/schemas/Enrollments"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -424,7 +419,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await Order_itemsDBApi.findBy( const payload = await EnrollmentsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
); );

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const OrdersService = require('../services/orders'); const LessonsService = require('../services/lessons');
const OrdersDBApi = require('../db/api/orders'); const LessonsDBApi = require('../db/api/lessons');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
@ -15,47 +15,48 @@ const {
checkCrudPermissions, checkCrudPermissions,
} = require('../middlewares/check-permissions'); } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('orders')); router.use(checkCrudPermissions('lessons'));
/** /**
* @swagger * @swagger
* components: * components:
* schemas: * schemas:
* Orders: * Lessons:
* type: object * type: object
* properties: * properties:
* order_number: * title:
* type: string * type: string
* default: order_number * default: title
* shipping_address: * content:
* type: string * type: string
* default: shipping_address * default: content
* order:
* total: * type: integer
* format: int64
* duration:
* type: integer * type: integer
* format: int64 * format: int64
*
*
*/ */
/** /**
* @swagger * @swagger
* tags: * tags:
* name: Orders * name: Lessons
* description: The Orders managing API * description: The Lessons managing API
*/ */
/** /**
* @swagger * @swagger
* /api/orders: * /api/lessons:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Add new item * summary: Add new item
* description: Add new item * description: Add new item
* requestBody: * requestBody:
@ -67,14 +68,14 @@ router.use(checkCrudPermissions('orders'));
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* responses: * responses:
* 200: * 200:
* description: The item was successfully added * description: The item was successfully added
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -85,7 +86,7 @@ router.use(checkCrudPermissions('orders'));
router.post('/', wrapAsync(async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
@ -96,7 +97,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Bulk import items * summary: Bulk import items
* description: Bulk import items * description: Bulk import items
* requestBody: * requestBody:
@ -109,14 +110,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items * description: Data of the updated items
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* responses: * responses:
* 200: * 200:
* description: The items were successfully imported * description: The items were successfully imported
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -128,18 +129,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => { router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); const link = new URL(referer);
await OrdersService.bulkImport(req, res, true, link.host); await LessonsService.bulkImport(req, res, true, link.host);
const payload = true; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/orders/{id}: * /api/lessons/{id}:
* put: * put:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Update the data of the selected item * summary: Update the data of the selected item
* description: Update the data of the selected item * description: Update the data of the selected item
* parameters: * parameters:
@ -162,7 +163,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* required: * required:
* - id * - id
* responses: * responses:
@ -171,7 +172,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -182,18 +183,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.put('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/orders/{id}: * /api/lessons/{id}:
* delete: * delete:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Delete the selected item * summary: Delete the selected item
* description: Delete the selected item * description: Delete the selected item
* parameters: * parameters:
@ -209,7 +210,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -220,18 +221,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.delete('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/orders/deleteByIds: * /api/lessons/deleteByIds:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Delete the selected item list * summary: Delete the selected item list
* description: Delete the selected item list * description: Delete the selected item list
* requestBody: * requestBody:
@ -249,7 +250,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -258,29 +259,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.post('/deleteByIds', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/orders: * /api/lessons:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Get all orders * summary: Get all lessons
* description: Get all orders * description: Get all lessons
* responses: * responses:
* 200: * 200:
* description: Orders list successfully received * description: Lessons list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -292,14 +293,14 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype const filetype = req.query.filetype
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await OrdersDBApi.findAll( const payload = await LessonsDBApi.findAll(
req.query, { currentUser } req.query, { currentUser }
); );
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id','order_number','shipping_address', const fields = ['id','title','content',
'order','duration',
'total', 'start_at','end_at',
'placed_at','shipped_at','delivery_date',
]; ];
const opts = { fields }; const opts = { fields };
try { try {
@ -318,22 +319,22 @@ router.get('/', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/orders/count: * /api/lessons/count:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Count all orders * summary: Count all lessons
* description: Count all orders * description: Count all lessons
* responses: * responses:
* 200: * 200:
* description: Orders count successfully received * description: Lessons count successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -344,7 +345,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await OrdersDBApi.findAll( const payload = await LessonsDBApi.findAll(
req.query, req.query,
null, null,
{ countOnly: true, currentUser } { countOnly: true, currentUser }
@ -355,22 +356,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/orders/autocomplete: * /api/lessons/autocomplete:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Find all orders that match search criteria * summary: Find all lessons that match search criteria
* description: Find all orders that match search criteria * description: Find all lessons that match search criteria
* responses: * responses:
* 200: * 200:
* description: Orders list successfully received * description: Lessons list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -380,7 +381,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/ */
router.get('/autocomplete', async (req, res) => { router.get('/autocomplete', async (req, res) => {
const payload = await OrdersDBApi.findAllAutocomplete( const payload = await LessonsDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
@ -392,11 +393,11 @@ router.get('/autocomplete', async (req, res) => {
/** /**
* @swagger * @swagger
* /api/orders/{id}: * /api/lessons/{id}:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Orders] * tags: [Lessons]
* summary: Get selected item * summary: Get selected item
* description: Get selected item * description: Get selected item
* parameters: * parameters:
@ -412,7 +413,7 @@ router.get('/autocomplete', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Orders" * $ref: "#/components/schemas/Lessons"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -423,7 +424,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await OrdersDBApi.findBy( const payload = await LessonsDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
); );

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const PaymentsService = require('../services/payments'); const ProgressService = require('../services/progress');
const PaymentsDBApi = require('../db/api/payments'); const ProgressDBApi = require('../db/api/progress');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
@ -15,44 +15,45 @@ const {
checkCrudPermissions, checkCrudPermissions,
} = require('../middlewares/check-permissions'); } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('payments')); router.use(checkCrudPermissions('progress'));
/** /**
* @swagger * @swagger
* components: * components:
* schemas: * schemas:
* Payments: * Progress:
* type: object * type: object
* properties: * properties:
* reference: * summary:
* type: string * type: string
* default: reference * default: summary
* notes:
* type: string
* default: notes
* amount: * percent:
* type: integer * type: integer
* format: int64 * format: int64
*
*
*/ */
/** /**
* @swagger * @swagger
* tags: * tags:
* name: Payments * name: Progress
* description: The Payments managing API * description: The Progress managing API
*/ */
/** /**
* @swagger * @swagger
* /api/payments: * /api/progress:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Add new item * summary: Add new item
* description: Add new item * description: Add new item
* requestBody: * requestBody:
@ -64,14 +65,14 @@ router.use(checkCrudPermissions('payments'));
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* responses: * responses:
* 200: * 200:
* description: The item was successfully added * description: The item was successfully added
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -82,7 +83,7 @@ router.use(checkCrudPermissions('payments'));
router.post('/', wrapAsync(async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
@ -93,7 +94,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Bulk import items * summary: Bulk import items
* description: Bulk import items * description: Bulk import items
* requestBody: * requestBody:
@ -106,14 +107,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items * description: Data of the updated items
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* responses: * responses:
* 200: * 200:
* description: The items were successfully imported * description: The items were successfully imported
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -125,18 +126,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => { router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); const link = new URL(referer);
await PaymentsService.bulkImport(req, res, true, link.host); await ProgressService.bulkImport(req, res, true, link.host);
const payload = true; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/payments/{id}: * /api/progress/{id}:
* put: * put:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Update the data of the selected item * summary: Update the data of the selected item
* description: Update the data of the selected item * description: Update the data of the selected item
* parameters: * parameters:
@ -159,7 +160,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* required: * required:
* - id * - id
* responses: * responses:
@ -168,7 +169,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -179,18 +180,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.put('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/payments/{id}: * /api/progress/{id}:
* delete: * delete:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Delete the selected item * summary: Delete the selected item
* description: Delete the selected item * description: Delete the selected item
* parameters: * parameters:
@ -206,7 +207,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -217,18 +218,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.delete('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/payments/deleteByIds: * /api/progress/deleteByIds:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Delete the selected item list * summary: Delete the selected item list
* description: Delete the selected item list * description: Delete the selected item list
* requestBody: * requestBody:
@ -246,7 +247,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -255,29 +256,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.post('/deleteByIds', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/payments: * /api/progress:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Get all payments * summary: Get all progress
* description: Get all payments * description: Get all progress
* responses: * responses:
* 200: * 200:
* description: Payments list successfully received * description: Progress list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -289,14 +290,14 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype const filetype = req.query.filetype
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await PaymentsDBApi.findAll( const payload = await ProgressDBApi.findAll(
req.query, { currentUser } req.query, { currentUser }
); );
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id','reference', const fields = ['id','summary','notes',
'amount', 'percent',
'paid_at', 'completed_at',
]; ];
const opts = { fields }; const opts = { fields };
try { try {
@ -315,22 +316,22 @@ router.get('/', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/payments/count: * /api/progress/count:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Count all payments * summary: Count all progress
* description: Count all payments * description: Count all progress
* responses: * responses:
* 200: * 200:
* description: Payments count successfully received * description: Progress count successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -341,7 +342,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await PaymentsDBApi.findAll( const payload = await ProgressDBApi.findAll(
req.query, req.query,
null, null,
{ countOnly: true, currentUser } { countOnly: true, currentUser }
@ -352,22 +353,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/payments/autocomplete: * /api/progress/autocomplete:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Find all payments that match search criteria * summary: Find all progress that match search criteria
* description: Find all payments that match search criteria * description: Find all progress that match search criteria
* responses: * responses:
* 200: * 200:
* description: Payments list successfully received * description: Progress list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -377,7 +378,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/ */
router.get('/autocomplete', async (req, res) => { router.get('/autocomplete', async (req, res) => {
const payload = await PaymentsDBApi.findAllAutocomplete( const payload = await ProgressDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
@ -389,11 +390,11 @@ router.get('/autocomplete', async (req, res) => {
/** /**
* @swagger * @swagger
* /api/payments/{id}: * /api/progress/{id}:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Payments] * tags: [Progress]
* summary: Get selected item * summary: Get selected item
* description: Get selected item * description: Get selected item
* parameters: * parameters:
@ -409,7 +410,7 @@ router.get('/autocomplete', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Payments" * $ref: "#/components/schemas/Progress"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -420,7 +421,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await PaymentsDBApi.findBy( const payload = await ProgressDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
); );

View File

@ -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;

View File

@ -1,8 +1,8 @@
const express = require('express'); const express = require('express');
const ProductsService = require('../services/products'); const QuizzesService = require('../services/quizzes');
const ProductsDBApi = require('../db/api/products'); const QuizzesDBApi = require('../db/api/quizzes');
const wrapAsync = require('../helpers').wrapAsync; const wrapAsync = require('../helpers').wrapAsync;
@ -15,52 +15,48 @@ const {
checkCrudPermissions, checkCrudPermissions,
} = require('../middlewares/check-permissions'); } = require('../middlewares/check-permissions');
router.use(checkCrudPermissions('products')); router.use(checkCrudPermissions('quizzes'));
/** /**
* @swagger * @swagger
* components: * components:
* schemas: * schemas:
* Products: * Quizzes:
* type: object * type: object
* properties: * properties:
* sku: * title:
* type: string * type: string
* default: sku * default: title
* name:
* type: string
* default: name
* description: * description:
* type: string * type: string
* default: description * default: description
* stock: * passing_score:
* type: integer
* format: int64
* time_limit:
* type: integer * type: integer
* format: int64 * format: int64
* price:
* type: integer
* format: int64
*
*/ */
/** /**
* @swagger * @swagger
* tags: * tags:
* name: Products * name: Quizzes
* description: The Products managing API * description: The Quizzes managing API
*/ */
/** /**
* @swagger * @swagger
* /api/products: * /api/quizzes:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Add new item * summary: Add new item
* description: Add new item * description: Add new item
* requestBody: * requestBody:
@ -72,14 +68,14 @@ router.use(checkCrudPermissions('products'));
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* responses: * responses:
* 200: * 200:
* description: The item was successfully added * description: The item was successfully added
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -90,7 +86,7 @@ router.use(checkCrudPermissions('products'));
router.post('/', wrapAsync(async (req, res) => { router.post('/', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
@ -101,7 +97,7 @@ router.post('/', wrapAsync(async (req, res) => {
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Bulk import items * summary: Bulk import items
* description: Bulk import items * description: Bulk import items
* requestBody: * requestBody:
@ -114,14 +110,14 @@ router.post('/', wrapAsync(async (req, res) => {
* description: Data of the updated items * description: Data of the updated items
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* responses: * responses:
* 200: * 200:
* description: The items were successfully imported * description: The items were successfully imported
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 405: * 405:
@ -133,18 +129,18 @@ router.post('/', wrapAsync(async (req, res) => {
router.post('/bulk-import', wrapAsync(async (req, res) => { router.post('/bulk-import', wrapAsync(async (req, res) => {
const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const referer = req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`;
const link = new URL(referer); const link = new URL(referer);
await ProductsService.bulkImport(req, res, true, link.host); await QuizzesService.bulkImport(req, res, true, link.host);
const payload = true; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/products/{id}: * /api/quizzes/{id}:
* put: * put:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Update the data of the selected item * summary: Update the data of the selected item
* description: Update the data of the selected item * description: Update the data of the selected item
* parameters: * parameters:
@ -167,7 +163,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* data: * data:
* description: Data of the updated item * description: Data of the updated item
* type: object * type: object
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* required: * required:
* - id * - id
* responses: * responses:
@ -176,7 +172,7 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -187,18 +183,18 @@ router.post('/bulk-import', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.put('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/products/{id}: * /api/quizzes/{id}:
* delete: * delete:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Delete the selected item * summary: Delete the selected item
* description: Delete the selected item * description: Delete the selected item
* parameters: * parameters:
@ -214,7 +210,7 @@ router.put('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -225,18 +221,18 @@ router.put('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.delete('/:id', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/products/deleteByIds: * /api/quizzes/deleteByIds:
* post: * post:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Delete the selected item list * summary: Delete the selected item list
* description: Delete the selected item list * description: Delete the selected item list
* requestBody: * requestBody:
@ -254,7 +250,7 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -263,29 +259,29 @@ router.delete('/:id', wrapAsync(async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.post('/deleteByIds', wrapAsync(async (req, res) => { 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; const payload = true;
res.status(200).send(payload); res.status(200).send(payload);
})); }));
/** /**
* @swagger * @swagger
* /api/products: * /api/quizzes:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Get all products * summary: Get all quizzes
* description: Get all products * description: Get all quizzes
* responses: * responses:
* 200: * 200:
* description: Products list successfully received * description: Quizzes list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -297,13 +293,13 @@ router.get('/', wrapAsync(async (req, res) => {
const filetype = req.query.filetype const filetype = req.query.filetype
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await ProductsDBApi.findAll( const payload = await QuizzesDBApi.findAll(
req.query, { currentUser } req.query, { currentUser }
); );
if (filetype && filetype === 'csv') { if (filetype && filetype === 'csv') {
const fields = ['id','sku','name','description', const fields = ['id','title','description',
'stock', 'passing_score','time_limit',
'price',
]; ];
const opts = { fields }; const opts = { fields };
@ -323,22 +319,22 @@ router.get('/', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/products/count: * /api/quizzes/count:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Count all products * summary: Count all quizzes
* description: Count all products * description: Count all quizzes
* responses: * responses:
* 200: * 200:
* description: Products count successfully received * description: Quizzes count successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -349,7 +345,7 @@ router.get('/', wrapAsync(async (req, res) => {
router.get('/count', wrapAsync(async (req, res) => { router.get('/count', wrapAsync(async (req, res) => {
const currentUser = req.currentUser; const currentUser = req.currentUser;
const payload = await ProductsDBApi.findAll( const payload = await QuizzesDBApi.findAll(
req.query, req.query,
null, null,
{ countOnly: true, currentUser } { countOnly: true, currentUser }
@ -360,22 +356,22 @@ router.get('/count', wrapAsync(async (req, res) => {
/** /**
* @swagger * @swagger
* /api/products/autocomplete: * /api/quizzes/autocomplete:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Find all products that match search criteria * summary: Find all quizzes that match search criteria
* description: Find all products that match search criteria * description: Find all quizzes that match search criteria
* responses: * responses:
* 200: * 200:
* description: Products list successfully received * description: Quizzes list successfully received
* content: * content:
* application/json: * application/json:
* schema: * schema:
* type: array * type: array
* items: * items:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 401: * 401:
* $ref: "#/components/responses/UnauthorizedError" * $ref: "#/components/responses/UnauthorizedError"
* 404: * 404:
@ -385,7 +381,7 @@ router.get('/count', wrapAsync(async (req, res) => {
*/ */
router.get('/autocomplete', async (req, res) => { router.get('/autocomplete', async (req, res) => {
const payload = await ProductsDBApi.findAllAutocomplete( const payload = await QuizzesDBApi.findAllAutocomplete(
req.query.query, req.query.query,
req.query.limit, req.query.limit,
req.query.offset, req.query.offset,
@ -397,11 +393,11 @@ router.get('/autocomplete', async (req, res) => {
/** /**
* @swagger * @swagger
* /api/products/{id}: * /api/quizzes/{id}:
* get: * get:
* security: * security:
* - bearerAuth: [] * - bearerAuth: []
* tags: [Products] * tags: [Quizzes]
* summary: Get selected item * summary: Get selected item
* description: Get selected item * description: Get selected item
* parameters: * parameters:
@ -417,7 +413,7 @@ router.get('/autocomplete', async (req, res) => {
* content: * content:
* application/json: * application/json:
* schema: * schema:
* $ref: "#/components/schemas/Products" * $ref: "#/components/schemas/Quizzes"
* 400: * 400:
* description: Invalid ID supplied * description: Invalid ID supplied
* 401: * 401:
@ -428,7 +424,7 @@ router.get('/autocomplete', async (req, res) => {
* description: Some server error * description: Some server error
*/ */
router.get('/:id', wrapAsync(async (req, res) => { router.get('/:id', wrapAsync(async (req, res) => {
const payload = await ProductsDBApi.findBy( const payload = await QuizzesDBApi.findBy(
{ id: req.params.id }, { id: req.params.id },
); );

View File

@ -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;
}
}
};

View File

@ -1,5 +1,5 @@
const db = require('../db/models'); 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 processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser'); 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) { static async create(data, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await Order_itemsDBApi.create( await CertificatesDBApi.create(
data, data,
{ {
currentUser, currentUser,
@ -51,7 +51,7 @@ module.exports = class Order_itemsService {
.on('error', (error) => reject(error)); .on('error', (error) => reject(error));
}) })
await Order_itemsDBApi.bulkImport(results, { await CertificatesDBApi.bulkImport(results, {
transaction, transaction,
ignoreDuplicates: true, ignoreDuplicates: true,
validate: true, validate: true,
@ -68,18 +68,18 @@ module.exports = class Order_itemsService {
static async update(data, id, currentUser) { static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
let order_items = await Order_itemsDBApi.findBy( let certificates = await CertificatesDBApi.findBy(
{id}, {id},
{transaction}, {transaction},
); );
if (!order_items) { if (!certificates) {
throw new ValidationError( throw new ValidationError(
'order_itemsNotFound', 'certificatesNotFound',
); );
} }
const updatedOrder_items = await Order_itemsDBApi.update( const updatedCertificates = await CertificatesDBApi.update(
id, id,
data, data,
{ {
@ -89,7 +89,7 @@ module.exports = class Order_itemsService {
); );
await transaction.commit(); await transaction.commit();
return updatedOrder_items; return updatedCertificates;
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class Order_itemsService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await Order_itemsDBApi.deleteByIds(ids, { await CertificatesDBApi.deleteByIds(ids, {
currentUser, currentUser,
transaction, transaction,
}); });
@ -117,7 +117,7 @@ module.exports = class Order_itemsService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await Order_itemsDBApi.remove( await CertificatesDBApi.remove(
id, id,
{ {
currentUser, currentUser,

View File

@ -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;
}
}
};

View File

@ -1,5 +1,5 @@
const db = require('../db/models'); const db = require('../db/models');
const OrdersDBApi = require('../db/api/orders'); const CoursesDBApi = require('../db/api/courses');
const processFile = require("../middlewares/upload"); const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser'); 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) { static async create(data, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await OrdersDBApi.create( await CoursesDBApi.create(
data, data,
{ {
currentUser, currentUser,
@ -51,7 +51,7 @@ module.exports = class OrdersService {
.on('error', (error) => reject(error)); .on('error', (error) => reject(error));
}) })
await OrdersDBApi.bulkImport(results, { await CoursesDBApi.bulkImport(results, {
transaction, transaction,
ignoreDuplicates: true, ignoreDuplicates: true,
validate: true, validate: true,
@ -68,18 +68,18 @@ module.exports = class OrdersService {
static async update(data, id, currentUser) { static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
let orders = await OrdersDBApi.findBy( let courses = await CoursesDBApi.findBy(
{id}, {id},
{transaction}, {transaction},
); );
if (!orders) { if (!courses) {
throw new ValidationError( throw new ValidationError(
'ordersNotFound', 'coursesNotFound',
); );
} }
const updatedOrders = await OrdersDBApi.update( const updatedCourses = await CoursesDBApi.update(
id, id,
data, data,
{ {
@ -89,7 +89,7 @@ module.exports = class OrdersService {
); );
await transaction.commit(); await transaction.commit();
return updatedOrders; return updatedCourses;
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class OrdersService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await OrdersDBApi.deleteByIds(ids, { await CoursesDBApi.deleteByIds(ids, {
currentUser, currentUser,
transaction, transaction,
}); });
@ -117,7 +117,7 @@ module.exports = class OrdersService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await OrdersDBApi.remove( await CoursesDBApi.remove(
id, id,
{ {
currentUser, currentUser,

View File

@ -1,5 +1,5 @@
const db = require('../db/models'); const db = require('../db/models');
const CategoriesDBApi = require('../db/api/categories'); const EnrollmentsDBApi = require('../db/api/enrollments');
const processFile = require("../middlewares/upload"); const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser'); 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) { static async create(data, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await CategoriesDBApi.create( await EnrollmentsDBApi.create(
data, data,
{ {
currentUser, currentUser,
@ -51,7 +51,7 @@ module.exports = class CategoriesService {
.on('error', (error) => reject(error)); .on('error', (error) => reject(error));
}) })
await CategoriesDBApi.bulkImport(results, { await EnrollmentsDBApi.bulkImport(results, {
transaction, transaction,
ignoreDuplicates: true, ignoreDuplicates: true,
validate: true, validate: true,
@ -68,18 +68,18 @@ module.exports = class CategoriesService {
static async update(data, id, currentUser) { static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
let categories = await CategoriesDBApi.findBy( let enrollments = await EnrollmentsDBApi.findBy(
{id}, {id},
{transaction}, {transaction},
); );
if (!categories) { if (!enrollments) {
throw new ValidationError( throw new ValidationError(
'categoriesNotFound', 'enrollmentsNotFound',
); );
} }
const updatedCategories = await CategoriesDBApi.update( const updatedEnrollments = await EnrollmentsDBApi.update(
id, id,
data, data,
{ {
@ -89,7 +89,7 @@ module.exports = class CategoriesService {
); );
await transaction.commit(); await transaction.commit();
return updatedCategories; return updatedEnrollments;
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class CategoriesService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await CategoriesDBApi.deleteByIds(ids, { await EnrollmentsDBApi.deleteByIds(ids, {
currentUser, currentUser,
transaction, transaction,
}); });
@ -117,7 +117,7 @@ module.exports = class CategoriesService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await CategoriesDBApi.remove( await EnrollmentsDBApi.remove(
id, id,
{ {
currentUser, currentUser,

View File

@ -1,5 +1,5 @@
const db = require('../db/models'); const db = require('../db/models');
const ProductsDBApi = require('../db/api/products'); const LessonsDBApi = require('../db/api/lessons');
const processFile = require("../middlewares/upload"); const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser'); 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) { static async create(data, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await ProductsDBApi.create( await LessonsDBApi.create(
data, data,
{ {
currentUser, currentUser,
@ -51,7 +51,7 @@ module.exports = class ProductsService {
.on('error', (error) => reject(error)); .on('error', (error) => reject(error));
}) })
await ProductsDBApi.bulkImport(results, { await LessonsDBApi.bulkImport(results, {
transaction, transaction,
ignoreDuplicates: true, ignoreDuplicates: true,
validate: true, validate: true,
@ -68,18 +68,18 @@ module.exports = class ProductsService {
static async update(data, id, currentUser) { static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
let products = await ProductsDBApi.findBy( let lessons = await LessonsDBApi.findBy(
{id}, {id},
{transaction}, {transaction},
); );
if (!products) { if (!lessons) {
throw new ValidationError( throw new ValidationError(
'productsNotFound', 'lessonsNotFound',
); );
} }
const updatedProducts = await ProductsDBApi.update( const updatedLessons = await LessonsDBApi.update(
id, id,
data, data,
{ {
@ -89,7 +89,7 @@ module.exports = class ProductsService {
); );
await transaction.commit(); await transaction.commit();
return updatedProducts; return updatedLessons;
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class ProductsService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await ProductsDBApi.deleteByIds(ids, { await LessonsDBApi.deleteByIds(ids, {
currentUser, currentUser,
transaction, transaction,
}); });
@ -117,7 +117,7 @@ module.exports = class ProductsService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await ProductsDBApi.remove( await LessonsDBApi.remove(
id, id,
{ {
currentUser, currentUser,

View File

@ -1,6 +1,6 @@
const errors = { const errors = {
app: { app: {
title: 'Store Operations Manager', title: 'CourseFlow LMS',
}, },
auth: { auth: {

View File

@ -1,5 +1,5 @@
const db = require('../db/models'); const db = require('../db/models');
const PaymentsDBApi = require('../db/api/payments'); const ProgressDBApi = require('../db/api/progress');
const processFile = require("../middlewares/upload"); const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser'); 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) { static async create(data, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await PaymentsDBApi.create( await ProgressDBApi.create(
data, data,
{ {
currentUser, currentUser,
@ -51,7 +51,7 @@ module.exports = class PaymentsService {
.on('error', (error) => reject(error)); .on('error', (error) => reject(error));
}) })
await PaymentsDBApi.bulkImport(results, { await ProgressDBApi.bulkImport(results, {
transaction, transaction,
ignoreDuplicates: true, ignoreDuplicates: true,
validate: true, validate: true,
@ -68,18 +68,18 @@ module.exports = class PaymentsService {
static async update(data, id, currentUser) { static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
let payments = await PaymentsDBApi.findBy( let progress = await ProgressDBApi.findBy(
{id}, {id},
{transaction}, {transaction},
); );
if (!payments) { if (!progress) {
throw new ValidationError( throw new ValidationError(
'paymentsNotFound', 'progressNotFound',
); );
} }
const updatedPayments = await PaymentsDBApi.update( const updatedProgress = await ProgressDBApi.update(
id, id,
data, data,
{ {
@ -89,7 +89,7 @@ module.exports = class PaymentsService {
); );
await transaction.commit(); await transaction.commit();
return updatedPayments; return updatedProgress;
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class PaymentsService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await PaymentsDBApi.deleteByIds(ids, { await ProgressDBApi.deleteByIds(ids, {
currentUser, currentUser,
transaction, transaction,
}); });
@ -117,7 +117,7 @@ module.exports = class PaymentsService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await PaymentsDBApi.remove( await ProgressDBApi.remove(
id, id,
{ {
currentUser, currentUser,

View File

@ -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;
}
}
};

View File

@ -1,5 +1,5 @@
const db = require('../db/models'); const db = require('../db/models');
const CustomersDBApi = require('../db/api/customers'); const QuizzesDBApi = require('../db/api/quizzes');
const processFile = require("../middlewares/upload"); const processFile = require("../middlewares/upload");
const ValidationError = require('./notifications/errors/validation'); const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser'); 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) { static async create(data, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await CustomersDBApi.create( await QuizzesDBApi.create(
data, data,
{ {
currentUser, currentUser,
@ -51,7 +51,7 @@ module.exports = class CustomersService {
.on('error', (error) => reject(error)); .on('error', (error) => reject(error));
}) })
await CustomersDBApi.bulkImport(results, { await QuizzesDBApi.bulkImport(results, {
transaction, transaction,
ignoreDuplicates: true, ignoreDuplicates: true,
validate: true, validate: true,
@ -68,18 +68,18 @@ module.exports = class CustomersService {
static async update(data, id, currentUser) { static async update(data, id, currentUser) {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
let customers = await CustomersDBApi.findBy( let quizzes = await QuizzesDBApi.findBy(
{id}, {id},
{transaction}, {transaction},
); );
if (!customers) { if (!quizzes) {
throw new ValidationError( throw new ValidationError(
'customersNotFound', 'quizzesNotFound',
); );
} }
const updatedCustomers = await CustomersDBApi.update( const updatedQuizzes = await QuizzesDBApi.update(
id, id,
data, data,
{ {
@ -89,7 +89,7 @@ module.exports = class CustomersService {
); );
await transaction.commit(); await transaction.commit();
return updatedCustomers; return updatedQuizzes;
} catch (error) { } catch (error) {
await transaction.rollback(); await transaction.rollback();
@ -101,7 +101,7 @@ module.exports = class CustomersService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await CustomersDBApi.deleteByIds(ids, { await QuizzesDBApi.deleteByIds(ids, {
currentUser, currentUser,
transaction, transaction,
}); });
@ -117,7 +117,7 @@ module.exports = class CustomersService {
const transaction = await db.sequelize.transaction(); const transaction = await db.sequelize.transaction();
try { try {
await CustomersDBApi.remove( await QuizzesDBApi.remove(
id, id,
{ {
currentUser, currentUser,

View File

@ -66,12 +66,12 @@ module.exports = class SearchService {
"products": [ "course_categories": [
"sku",
"name", "name",
"slug",
"description", "description",
], ],
@ -81,12 +81,16 @@ module.exports = class SearchService {
"categories": [ "courses": [
"name", "title",
"short_description",
"description", "description",
"language",
], ],
@ -94,19 +98,48 @@ module.exports = class SearchService {
"customers": [ "lessons": [
"name", "title",
"email", "content",
"phone", ],
"address",
"enrollments": [
"enrollment_code",
],
"progress": [
"summary",
"notes", "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", "serial",
],
"shipments": [
"carrier",
"tracking_number",
], ],
@ -171,11 +195,59 @@ module.exports = class SearchService {
"products": [
"courses": [
"price", "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",
],
}; };

View File

@ -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;
}
}
};

View File

@ -2,7 +2,7 @@ const chokidar = require('chokidar');
const { exec } = require('child_process'); const { exec } = require('child_process');
const nodemon = require('nodemon'); const nodemon = require('nodemon');
const nodeEnv = 'dev_stage'; const nodeEnv = process.env.NODE_ENV || 'dev_stage';
const childEnv = { ...process.env, NODE_ENV: nodeEnv }; const childEnv = { ...process.env, NODE_ENV: nodeEnv };
const migrationsWatcher = chokidar.watch('./src/db/migrations', { const migrationsWatcher = chokidar.watch('./src/db/migrations', {

View File

@ -25,7 +25,7 @@ services:
- ./data/db:/var/lib/postgresql/data - ./data/db:/var/lib/postgresql/data
environment: environment:
- POSTGRES_HOST_AUTH_METHOD=trust - POSTGRES_HOST_AUTH_METHOD=trust
- POSTGRES_DB=db_store_operations_manager - POSTGRES_DB=db_courseflow_lms
ports: ports:
- "5432:5432" - "5432:5432"
logging: logging:

View File

@ -1,4 +1,4 @@
# Store Operations Manager # CourseFlow LMS
## This project was generated by Flatlogic Platform. ## This project was generated by Flatlogic Platform.
## Install ## Install

View File

@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
shipments: any[]; announcements: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -20,8 +20,8 @@ type Props = {
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
}; };
const CardShipments = ({ const CardAnnouncements = ({
shipments, announcements,
loading, loading,
onDelete, onDelete,
currentPage, currentPage,
@ -37,7 +37,7 @@ const CardShipments = ({
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser); const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_SHIPMENTS') const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ANNOUNCEMENTS')
return ( return (
@ -47,7 +47,7 @@ const CardShipments = ({
role='list' 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' 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) => (
<li <li
key={item.id} key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${ className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
@ -57,8 +57,8 @@ const CardShipments = ({
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}> <div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/shipments/shipments-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'> <Link href={`/announcements/announcements-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.tracking_number} {item.title}
</Link> </Link>
@ -66,8 +66,8 @@ const CardShipments = ({
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/shipments/shipments-edit/?id=${item.id}`} pathEdit={`/announcements/announcements-edit/?id=${item.id}`}
pathView={`/shipments/shipments-view/?id=${item.id}`} pathView={`/announcements/announcements-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -78,10 +78,10 @@ const CardShipments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Order</dt> <dt className=' text-gray-500 dark:text-dark-600'>Course</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.ordersOneListFormatter(item.order) } { dataFormatter.coursesOneListFormatter(item.course) }
</div> </div>
</dd> </dd>
</div> </div>
@ -90,10 +90,10 @@ const CardShipments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Carrier</dt> <dt className=' text-gray-500 dark:text-dark-600'>Title</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.carrier } { item.title }
</div> </div>
</dd> </dd>
</div> </div>
@ -102,10 +102,10 @@ const CardShipments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>TrackingNumber</dt> <dt className=' text-gray-500 dark:text-dark-600'>Content</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.tracking_number } { item.content }
</div> </div>
</dd> </dd>
</div> </div>
@ -114,10 +114,10 @@ const CardShipments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ShippedAt</dt> <dt className=' text-gray-500 dark:text-dark-600'>PublishedAt</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.shipped_at) } { dataFormatter.dateTimeFormatter(item.published_at) }
</div> </div>
</dd> </dd>
</div> </div>
@ -126,22 +126,10 @@ const CardShipments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>DeliveredAt</dt> <dt className=' text-gray-500 dark:text-dark-600'>Active</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.delivered_at) } { dataFormatter.booleanFormatter(item.is_active) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Status</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.status }
</div> </div>
</dd> </dd>
</div> </div>
@ -151,7 +139,7 @@ const CardShipments = ({
</dl> </dl>
</li> </li>
))} ))}
{!loading && shipments.length === 0 && ( {!loading && announcements.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -168,4 +156,4 @@ const CardShipments = ({
); );
}; };
export default CardShipments; export default CardAnnouncements;

View File

@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
order_items: any[]; announcements: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -21,10 +21,10 @@ type Props = {
onPageChange: (page: number) => void; 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 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 corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor); const bgColor = useAppSelector((state) => state.style.cardsColor);
@ -34,13 +34,13 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
<> <>
<div className='relative overflow-x-auto p-4 space-y-4'> <div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{!loading && order_items.map((item) => ( {!loading && announcements.map((item) => (
<div key={item.id}> <div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}> <CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}> <div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link <Link
href={`/order_items/order_items-view/?id=${item.id}`} href={`/announcements/announcements-view/?id=${item.id}`}
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
@ -48,48 +48,40 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Name</p> <p className={'text-xs text-gray-500 '}>Course</p>
<p className={'line-clamp-2'}>{ item.name }</p> <p className={'line-clamp-2'}>{ dataFormatter.coursesOneListFormatter(item.course) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Order</p> <p className={'text-xs text-gray-500 '}>Title</p>
<p className={'line-clamp-2'}>{ dataFormatter.ordersOneListFormatter(item.order) }</p> <p className={'line-clamp-2'}>{ item.title }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Product</p> <p className={'text-xs text-gray-500 '}>Content</p>
<p className={'line-clamp-2'}>{ dataFormatter.productsOneListFormatter(item.product) }</p> <p className={'line-clamp-2'}>{ item.content }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Quantity</p> <p className={'text-xs text-gray-500 '}>PublishedAt</p>
<p className={'line-clamp-2'}>{ item.quantity }</p> <p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.published_at) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>UnitPrice</p> <p className={'text-xs text-gray-500 '}>Active</p>
<p className={'line-clamp-2'}>{ item.unit_price }</p> <p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.is_active) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>TotalPrice</p>
<p className={'line-clamp-2'}>{ item.total_price }</p>
</div> </div>
@ -98,8 +90,8 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/order_items/order_items-edit/?id=${item.id}`} pathEdit={`/announcements/announcements-edit/?id=${item.id}`}
pathView={`/order_items/order_items-view/?id=${item.id}`} pathView={`/announcements/announcements-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -108,7 +100,7 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
</CardBox> </CardBox>
</div> </div>
))} ))}
{!loading && order_items.length === 0 && ( {!loading && announcements.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -125,4 +117,4 @@ const ListOrder_items = ({ order_items, loading, onDelete, currentPage, numPages
) )
}; };
export default ListOrder_items export default ListAnnouncements

View File

@ -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<GridColDef[]>([]);
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 = (
<div className='relative overflow-x-auto'>
<DataGrid
autoHeight
rowHeight={64}
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `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);
}}
/>
</div>
)
return (
<>
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
<CardBox>
<Formik
initialValues={{
checkboxes: ['lorem'],
switches: ['lorem'],
radio: 'lorem',
}}
onSubmit={() => null}
>
<Form>
<>
{filterItems && filterItems.map((filterItem) => {
return (
<div key={filterItem.id} className="flex mb-4">
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Filter</div>
<Field
className={controlClasses}
name='selectedField'
id='selectedField'
component='select'
value={filterItem?.fields?.selectedField || ''}
onChange={handleChange(filterItem.id)}
>
{filters.map((selectOption) => (
<option
key={selectOption.title}
value={`${selectOption.title}`}
>
{selectOption.label}
</option>
))}
</Field>
</div>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.type === 'enum' ? (
<div className="flex flex-col w-full mr-3">
<div className="text-gray-500 font-bold">
Value
</div>
<Field
className={controlClasses}
name="filterValue"
id='filterValue'
component="select"
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
>
<option value="">Select Value</option>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Field>
</div>
) : filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.number ? (
<div className="flex flex-row w-full mr-3">
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">From</div>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
value={filterItem?.fields?.filterValueFrom || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
<div className="flex flex-col w-full">
<div className=" text-gray-500 font-bold">To</div>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : filters.find(
(filter) =>
filter.title ===
filterItem?.fields?.selectedField
)?.date ? (
<div className='flex flex-row w-full mr-3'>
<div className='flex flex-col w-full mr-3'>
<div className=' text-gray-500 font-bold'>
From
</div>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
type='datetime-local'
value={filterItem?.fields?.filterValueFrom || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
<div className='flex flex-col w-full'>
<div className=' text-gray-500 font-bold'>To</div>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
type='datetime-local'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : (
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Contains</div>
<Field
className={controlClasses}
name='filterValue'
placeholder='Contained'
id='filterValue'
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
)}
<div className="flex flex-col">
<div className=" text-gray-500 font-bold">Action</div>
<BaseButton
className="my-2"
type='reset'
color='danger'
label='Delete'
onClick={() => {
deleteFilter(filterItem.id)
}}
/>
</div>
</div>
)
})}
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
label='Cancel'
onClick={handleReset}
/>
</div>
</>
</Form>
</Formik>
</CardBox> : null
}
<CardBoxModal
title="Please confirm"
buttonColor="info"
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
isActive={isModalTrashActive}
onConfirm={handleDeleteAction}
onCancel={handleModalAction}
>
<p>Are you sure you want to delete this item?</p>
</CardBoxModal>
{announcements && Array.isArray(announcements) && !showGrid && (
<ListAnnouncements
announcements={announcements}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{showGrid && dataGrid}
{selectedRows.length > 0 &&
createPortal(
<BaseButton
className='me-4'
color='danger'
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
onClick={() => onDeleteRows(selectedRows)}
/>,
document.getElementById('delete-rows-button'),
)}
<ToastContainer />
</>
)
}
export default TableSampleAnnouncements

View File

@ -37,13 +37,13 @@ export const loadColumns = async (
} }
} }
const hasUpdatePermission = hasPermission(user, 'UPDATE_SHIPMENTS') const hasUpdatePermission = hasPermission(user, 'UPDATE_ANNOUNCEMENTS')
return [ return [
{ {
field: 'order', field: 'course',
headerName: 'Order', headerName: 'Course',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -57,15 +57,15 @@ export const loadColumns = async (
type: 'singleSelect', type: 'singleSelect',
getOptionValue: (value: any) => value?.id, getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label, getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('orders'), valueOptions: await callOptionsApi('courses'),
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value, params?.value?.id ?? params?.value,
}, },
{ {
field: 'carrier', field: 'title',
headerName: 'Carrier', headerName: 'Title',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -79,8 +79,8 @@ export const loadColumns = async (
}, },
{ {
field: 'tracking_number', field: 'content',
headerName: 'TrackingNumber', headerName: 'Content',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -94,8 +94,8 @@ export const loadColumns = async (
}, },
{ {
field: 'shipped_at', field: 'published_at',
headerName: 'ShippedAt', headerName: 'PublishedAt',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -107,31 +107,13 @@ export const loadColumns = async (
type: 'dateTime', type: 'dateTime',
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.shipped_at), new Date(params.row.published_at),
}, },
{ {
field: 'delivered_at', field: 'is_active',
headerName: 'DeliveredAt', headerName: 'Active',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.delivered_at),
},
{
field: 'status',
headerName: 'Status',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -141,6 +123,7 @@ export const loadColumns = async (
editable: hasUpdatePermission, editable: hasUpdatePermission,
type: 'boolean',
}, },
@ -157,8 +140,8 @@ export const loadColumns = async (
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={params?.row?.id} itemId={params?.row?.id}
pathEdit={`/shipments/shipments-edit/?id=${params?.row?.id}`} pathEdit={`/announcements/announcements-edit/?id=${params?.row?.id}`}
pathView={`/shipments/shipments-view/?id=${params?.row?.id}`} pathView={`/announcements/announcements-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}

View File

@ -39,7 +39,7 @@ export default function AsideMenuLayer({ menu, className = '', ...props }: Props
> >
<div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0"> <div className="text-center flex-1 lg:text-left lg:pl-6 xl:text-center xl:pl-0">
<b className="font-black">Store Operations Manager</b> <b className="font-black">CourseFlow LMS</b>
</div> </div>

View File

@ -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 (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
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 && certificates.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/certificates/certificates-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.serial}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/certificates/certificates-edit/?id=${item.id}`}
pathView={`/certificates/certificates-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Serial</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.serial }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Student</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.usersOneListFormatter(item.student) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Course</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.coursesOneListFormatter(item.course) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>IssuedAt</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.issued_at) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>File</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium'>
{dataFormatter.filesFormatter(item.file).map(link => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</div>
</dd>
</div>
</dl>
</li>
))}
{!loading && certificates.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardCertificates;

View File

@ -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 (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && certificates.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/certificates/certificates-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Serial</p>
<p className={'line-clamp-2'}>{ item.serial }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Student</p>
<p className={'line-clamp-2'}>{ dataFormatter.usersOneListFormatter(item.student) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Course</p>
<p className={'line-clamp-2'}>{ dataFormatter.coursesOneListFormatter(item.course) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>IssuedAt</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.issued_at) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>File</p>
{dataFormatter.filesFormatter(item.file).map(link => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/certificates/certificates-edit/?id=${item.id}`}
pathView={`/certificates/certificates-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && certificates.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListCertificates

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton' import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal' import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox"; 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 { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
@ -12,7 +12,7 @@ import {
DataGrid, DataGrid,
GridColDef, GridColDef,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import {loadColumns} from "./configureOrder_itemsCols"; import {loadColumns} from "./configureCertificatesCols";
import _ from 'lodash'; import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter' import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles"; import {dataGridStyles} from "../../styles";
@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles";
const perPage = 10 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 notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch(); 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 { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -60,10 +60,10 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid
}; };
useEffect(() => { useEffect(() => {
if (order_itemsNotify.showNotification) { if (certificatesNotify.showNotification) {
notify(order_itemsNotify.typeNotification, order_itemsNotify.textNotification); notify(certificatesNotify.typeNotification, certificatesNotify.textNotification);
} }
}, [order_itemsNotify.showNotification]); }, [certificatesNotify.showNotification]);
useEffect(() => { useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
@ -177,7 +177,7 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid
loadColumns( loadColumns(
handleDeleteModalAction, handleDeleteModalAction,
`order_items`, `certificates`,
currentUser, currentUser,
).then((newCols) => setColumns(newCols)); ).then((newCols) => setColumns(newCols));
}, [currentUser]); }, [currentUser]);
@ -215,7 +215,7 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid
sx={dataGridStyles} sx={dataGridStyles}
className={'datagrid--table'} className={'datagrid--table'}
getRowClassName={() => `datagrid--row`} getRowClassName={() => `datagrid--row`}
rows={order_items ?? []} rows={certificates ?? []}
columns={columns} columns={columns}
initialState={{ initialState={{
pagination: { pagination: {
@ -460,4 +460,4 @@ const TableSampleOrder_items = ({ filterItems, setFilterItems, filters, showGrid
) )
} }
export default TableSampleOrder_items export default TableSampleCertificates

View File

@ -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 => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</>
),
},
{
field: 'actions',
type: 'actions',
minWidth: 30,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
getActions: (params: GridRowParams) => {
return [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/certificates/certificates-edit/?id=${params?.row?.id}`}
pathView={`/certificates/certificates-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
categories: any[]; course_categories: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -20,8 +20,8 @@ type Props = {
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
}; };
const CardCategories = ({ const CardCourse_categories = ({
categories, course_categories,
loading, loading,
onDelete, onDelete,
currentPage, currentPage,
@ -37,7 +37,7 @@ const CardCategories = ({
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser); const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CATEGORIES') const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSE_CATEGORIES')
return ( return (
@ -47,7 +47,7 @@ const CardCategories = ({
role='list' 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' 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) => (
<li <li
key={item.id} key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${ className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
@ -57,7 +57,7 @@ const CardCategories = ({
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}> <div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/categories/categories-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'> <Link href={`/course_categories/course_categories-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.name} {item.name}
</Link> </Link>
@ -66,8 +66,8 @@ const CardCategories = ({
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/categories/categories-edit/?id=${item.id}`} pathEdit={`/course_categories/course_categories-edit/?id=${item.id}`}
pathView={`/categories/categories-view/?id=${item.id}`} pathView={`/course_categories/course_categories-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -89,6 +89,18 @@ const CardCategories = ({
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Slug</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.slug }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Description</dt> <dt className=' text-gray-500 dark:text-dark-600'>Description</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
@ -100,22 +112,10 @@ const CardCategories = ({
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ParentCategory</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.categoriesOneListFormatter(item.parent) }
</div>
</dd>
</div>
</dl> </dl>
</li> </li>
))} ))}
{!loading && categories.length === 0 && ( {!loading && course_categories.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -132,4 +132,4 @@ const CardCategories = ({
); );
}; };
export default CardCategories; export default CardCourse_categories;

View File

@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
categories: any[]; course_categories: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -21,10 +21,10 @@ type Props = {
onPageChange: (page: number) => void; 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 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 corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor); const bgColor = useAppSelector((state) => state.style.cardsColor);
@ -34,13 +34,13 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
<> <>
<div className='relative overflow-x-auto p-4 space-y-4'> <div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{!loading && categories.map((item) => ( {!loading && course_categories.map((item) => (
<div key={item.id}> <div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}> <CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}> <div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link <Link
href={`/categories/categories-view/?id=${item.id}`} href={`/course_categories/course_categories-view/?id=${item.id}`}
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
@ -56,16 +56,16 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Description</p> <p className={'text-xs text-gray-500 '}>Slug</p>
<p className={'line-clamp-2'}>{ item.description }</p> <p className={'line-clamp-2'}>{ item.slug }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>ParentCategory</p> <p className={'text-xs text-gray-500 '}>Description</p>
<p className={'line-clamp-2'}>{ dataFormatter.categoriesOneListFormatter(item.parent) }</p> <p className={'line-clamp-2'}>{ item.description }</p>
</div> </div>
@ -74,8 +74,8 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/categories/categories-edit/?id=${item.id}`} pathEdit={`/course_categories/course_categories-edit/?id=${item.id}`}
pathView={`/categories/categories-view/?id=${item.id}`} pathView={`/course_categories/course_categories-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -84,7 +84,7 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
</CardBox> </CardBox>
</div> </div>
))} ))}
{!loading && categories.length === 0 && ( {!loading && course_categories.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -101,4 +101,4 @@ const ListCategories = ({ categories, loading, onDelete, currentPage, numPages,
) )
}; };
export default ListCategories export default ListCourse_categories

View File

@ -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<GridColDef[]>([]);
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 = (
<div className='relative overflow-x-auto'>
<DataGrid
autoHeight
rowHeight={64}
sx={dataGridStyles}
className={'datagrid--table'}
getRowClassName={() => `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);
}}
/>
</div>
)
return (
<>
{filterItems && Array.isArray( filterItems ) && filterItems.length ?
<CardBox>
<Formik
initialValues={{
checkboxes: ['lorem'],
switches: ['lorem'],
radio: 'lorem',
}}
onSubmit={() => null}
>
<Form>
<>
{filterItems && filterItems.map((filterItem) => {
return (
<div key={filterItem.id} className="flex mb-4">
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Filter</div>
<Field
className={controlClasses}
name='selectedField'
id='selectedField'
component='select'
value={filterItem?.fields?.selectedField || ''}
onChange={handleChange(filterItem.id)}
>
{filters.map((selectOption) => (
<option
key={selectOption.title}
value={`${selectOption.title}`}
>
{selectOption.label}
</option>
))}
</Field>
</div>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.type === 'enum' ? (
<div className="flex flex-col w-full mr-3">
<div className="text-gray-500 font-bold">
Value
</div>
<Field
className={controlClasses}
name="filterValue"
id='filterValue'
component="select"
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
>
<option value="">Select Value</option>
{filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.options?.map((option) => (
<option key={option} value={option}>
{option}
</option>
))}
</Field>
</div>
) : filters.find((filter) =>
filter.title === filterItem?.fields?.selectedField
)?.number ? (
<div className="flex flex-row w-full mr-3">
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">From</div>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
value={filterItem?.fields?.filterValueFrom || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
<div className="flex flex-col w-full">
<div className=" text-gray-500 font-bold">To</div>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : filters.find(
(filter) =>
filter.title ===
filterItem?.fields?.selectedField
)?.date ? (
<div className='flex flex-row w-full mr-3'>
<div className='flex flex-col w-full mr-3'>
<div className=' text-gray-500 font-bold'>
From
</div>
<Field
className={controlClasses}
name='filterValueFrom'
placeholder='From'
id='filterValueFrom'
type='datetime-local'
value={filterItem?.fields?.filterValueFrom || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
<div className='flex flex-col w-full'>
<div className=' text-gray-500 font-bold'>To</div>
<Field
className={controlClasses}
name='filterValueTo'
placeholder='to'
id='filterValueTo'
type='datetime-local'
value={filterItem?.fields?.filterValueTo || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
</div>
) : (
<div className="flex flex-col w-full mr-3">
<div className=" text-gray-500 font-bold">Contains</div>
<Field
className={controlClasses}
name='filterValue'
placeholder='Contained'
id='filterValue'
value={filterItem?.fields?.filterValue || ''}
onChange={handleChange(filterItem.id)}
/>
</div>
)}
<div className="flex flex-col">
<div className=" text-gray-500 font-bold">Action</div>
<BaseButton
className="my-2"
type='reset'
color='danger'
label='Delete'
onClick={() => {
deleteFilter(filterItem.id)
}}
/>
</div>
</div>
)
})}
<div className="flex">
<BaseButton
className="my-2 mr-3"
color="success"
label='Apply'
onClick={handleSubmit}
/>
<BaseButton
className="my-2"
color='info'
label='Cancel'
onClick={handleReset}
/>
</div>
</>
</Form>
</Formik>
</CardBox> : null
}
<CardBoxModal
title="Please confirm"
buttonColor="info"
buttonLabel={loading ? 'Deleting...' : 'Confirm'}
isActive={isModalTrashActive}
onConfirm={handleDeleteAction}
onCancel={handleModalAction}
>
<p>Are you sure you want to delete this item?</p>
</CardBoxModal>
{course_categories && Array.isArray(course_categories) && !showGrid && (
<ListCourse_categories
course_categories={course_categories}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{showGrid && dataGrid}
{selectedRows.length > 0 &&
createPortal(
<BaseButton
className='me-4'
color='danger'
label={`Delete ${selectedRows.length === 1 ? 'Row' : 'Rows'}`}
onClick={() => onDeleteRows(selectedRows)}
/>,
document.getElementById('delete-rows-button'),
)}
<ToastContainer />
</>
)
}
export default TableSampleCourse_categories

View File

@ -37,7 +37,7 @@ export const loadColumns = async (
} }
} }
const hasUpdatePermission = hasPermission(user, 'UPDATE_CATEGORIES') const hasUpdatePermission = hasPermission(user, 'UPDATE_COURSE_CATEGORIES')
return [ return [
@ -54,6 +54,21 @@ export const loadColumns = async (
editable: hasUpdatePermission, 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', field: 'actions',
type: 'actions', type: 'actions',
@ -106,8 +99,8 @@ export const loadColumns = async (
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={params?.row?.id} itemId={params?.row?.id}
pathEdit={`/categories/categories-edit/?id=${params?.row?.id}`} pathEdit={`/course_categories/course_categories-edit/?id=${params?.row?.id}`}
pathView={`/categories/categories-view/?id=${params?.row?.id}`} pathView={`/course_categories/course_categories-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}

View File

@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
products: any[]; courses: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -20,8 +20,8 @@ type Props = {
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
}; };
const CardProducts = ({ const CardCourses = ({
products, courses,
loading, loading,
onDelete, onDelete,
currentPage, currentPage,
@ -37,7 +37,7 @@ const CardProducts = ({
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser); const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PRODUCTS') const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSES')
return ( return (
@ -47,7 +47,7 @@ const CardProducts = ({
role='list' 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' 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) => (
<li <li
key={item.id} key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${ className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
@ -58,16 +58,16 @@ const CardProducts = ({
<div className={`flex items-center ${bgColor} p-6 md:p-0 md:block gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}> <div className={`flex items-center ${bgColor} p-6 md:p-0 md:block gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link <Link
href={`/products/products-view/?id=${item.id}`} href={`/courses/courses-view/?id=${item.id}`}
className={'cursor-pointer'} className={'cursor-pointer'}
> >
<ImageField <ImageField
name={'Avatar'} name={'Avatar'}
image={item.images} image={item.thumbnail}
className='w-12 h-12 md:w-full md:h-44 rounded-lg md:rounded-b-none overflow-hidden ring-1 ring-gray-900/10' className='w-12 h-12 md:w-full md:h-44 rounded-lg md:rounded-b-none overflow-hidden ring-1 ring-gray-900/10'
imageClassName='h-full w-full flex-none rounded-lg md:rounded-b-none bg-white object-cover' imageClassName='h-full w-full flex-none rounded-lg md:rounded-b-none bg-white object-cover'
/> />
<p className={'px-6 py-2 font-semibold'}>{item.name}</p> <p className={'px-6 py-2 font-semibold'}>{item.title}</p>
</Link> </Link>
@ -75,8 +75,8 @@ const CardProducts = ({
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/products/products-edit/?id=${item.id}`} pathEdit={`/courses/courses-edit/?id=${item.id}`}
pathView={`/products/products-view/?id=${item.id}`} pathView={`/courses/courses-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -87,10 +87,10 @@ const CardProducts = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>SKU</dt> <dt className=' text-gray-500 dark:text-dark-600'>Title</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.sku } { item.title }
</div> </div>
</dd> </dd>
</div> </div>
@ -99,10 +99,10 @@ const CardProducts = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Name</dt> <dt className=' text-gray-500 dark:text-dark-600'>ShortDescription</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.name } { item.short_description }
</div> </div>
</dd> </dd>
</div> </div>
@ -122,6 +122,54 @@ const CardProducts = ({
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Instructor</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.usersOneListFormatter(item.instructor) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Category</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.course_categoriesOneListFormatter(item.category) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Level</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.level }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Language</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.language }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Price</dt> <dt className=' text-gray-500 dark:text-dark-600'>Price</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
@ -135,10 +183,10 @@ const CardProducts = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Stock</dt> <dt className=' text-gray-500 dark:text-dark-600'>Published</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.stock } { dataFormatter.booleanFormatter(item.published) }
</div> </div>
</dd> </dd>
</div> </div>
@ -147,12 +195,24 @@ const CardProducts = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Images</dt> <dt className=' text-gray-500 dark:text-dark-600'>PublishedAt</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.published_at) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Thumbnail</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium'> <div className='font-medium'>
<ImageField <ImageField
name={'Avatar'} name={'Avatar'}
image={item.images} image={item.thumbnail}
className='mx-auto w-8 h-8' className='mx-auto w-8 h-8'
/> />
</div> </div>
@ -163,22 +223,10 @@ const CardProducts = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Category</dt> <dt className=' text-gray-500 dark:text-dark-600'>Duration(minutes)</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.categoriesOneListFormatter(item.category) } { item.duration }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Status</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.status }
</div> </div>
</dd> </dd>
</div> </div>
@ -188,7 +236,7 @@ const CardProducts = ({
</dl> </dl>
</li> </li>
))} ))}
{!loading && products.length === 0 && ( {!loading && courses.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -205,4 +253,4 @@ const CardProducts = ({
); );
}; };
export default CardProducts; export default CardCourses;

View File

@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
products: any[]; courses: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -21,10 +21,10 @@ type Props = {
onPageChange: (page: number) => void; 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 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 corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor); const bgColor = useAppSelector((state) => state.style.cardsColor);
@ -34,20 +34,20 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
<> <>
<div className='relative overflow-x-auto p-4 space-y-4'> <div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{!loading && products.map((item) => ( {!loading && courses.map((item) => (
<div key={item.id}> <div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}> <CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}> <div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<ImageField <ImageField
name={'Avatar'} name={'Avatar'}
image={item.images} image={item.thumbnail}
className='w-24 h-24 rounded-l overflow-hidden hidden md:block' className='w-24 h-24 rounded-l overflow-hidden hidden md:block'
imageClassName={'rounded-l rounded-r-none h-full object-cover'} imageClassName={'rounded-l rounded-r-none h-full object-cover'}
/> />
<Link <Link
href={`/products/products-view/?id=${item.id}`} href={`/courses/courses-view/?id=${item.id}`}
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
@ -55,16 +55,16 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>SKU</p> <p className={'text-xs text-gray-500 '}>Title</p>
<p className={'line-clamp-2'}>{ item.sku }</p> <p className={'line-clamp-2'}>{ item.title }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Name</p> <p className={'text-xs text-gray-500 '}>ShortDescription</p>
<p className={'line-clamp-2'}>{ item.name }</p> <p className={'line-clamp-2'}>{ item.short_description }</p>
</div> </div>
@ -78,6 +78,38 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Instructor</p>
<p className={'line-clamp-2'}>{ dataFormatter.usersOneListFormatter(item.instructor) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Category</p>
<p className={'line-clamp-2'}>{ dataFormatter.course_categoriesOneListFormatter(item.category) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Level</p>
<p className={'line-clamp-2'}>{ item.level }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Language</p>
<p className={'line-clamp-2'}>{ item.language }</p>
</div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Price</p> <p className={'text-xs text-gray-500 '}>Price</p>
<p className={'line-clamp-2'}>{ item.price }</p> <p className={'line-clamp-2'}>{ item.price }</p>
@ -87,18 +119,26 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Stock</p> <p className={'text-xs text-gray-500 '}>Published</p>
<p className={'line-clamp-2'}>{ item.stock }</p> <p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.published) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Images</p> <p className={'text-xs text-gray-500 '}>PublishedAt</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.published_at) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Thumbnail</p>
<ImageField <ImageField
name={'Avatar'} name={'Avatar'}
image={item.images} image={item.thumbnail}
className='mx-auto w-8 h-8' className='mx-auto w-8 h-8'
/> />
</div> </div>
@ -107,16 +147,8 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Category</p> <p className={'text-xs text-gray-500 '}>Duration(minutes)</p>
<p className={'line-clamp-2'}>{ dataFormatter.categoriesOneListFormatter(item.category) }</p> <p className={'line-clamp-2'}>{ item.duration }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Status</p>
<p className={'line-clamp-2'}>{ item.status }</p>
</div> </div>
@ -125,8 +157,8 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/products/products-edit/?id=${item.id}`} pathEdit={`/courses/courses-edit/?id=${item.id}`}
pathView={`/products/products-view/?id=${item.id}`} pathView={`/courses/courses-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -135,7 +167,7 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
</CardBox> </CardBox>
</div> </div>
))} ))}
{!loading && products.length === 0 && ( {!loading && courses.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -152,4 +184,4 @@ const ListProducts = ({ products, loading, onDelete, currentPage, numPages, onPa
) )
}; };
export default ListProducts export default ListCourses

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton' import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal' import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox"; 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 { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
@ -12,18 +12,18 @@ import {
DataGrid, DataGrid,
GridColDef, GridColDef,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import {loadColumns} from "./configureCategoriesCols"; import {loadColumns} from "./configureCoursesCols";
import _ from 'lodash'; import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter' import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles"; import {dataGridStyles} from "../../styles";
import ListCategories from './ListCategories'; import CardCourses from './CardCourses';
const perPage = 10 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 notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch(); 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 { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -62,10 +62,10 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
}; };
useEffect(() => { useEffect(() => {
if (categoriesNotify.showNotification) { if (coursesNotify.showNotification) {
notify(categoriesNotify.typeNotification, categoriesNotify.textNotification); notify(coursesNotify.typeNotification, coursesNotify.textNotification);
} }
}, [categoriesNotify.showNotification]); }, [coursesNotify.showNotification]);
useEffect(() => { useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
@ -179,7 +179,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
loadColumns( loadColumns(
handleDeleteModalAction, handleDeleteModalAction,
`categories`, `courses`,
currentUser, currentUser,
).then((newCols) => setColumns(newCols)); ).then((newCols) => setColumns(newCols));
}, [currentUser]); }, [currentUser]);
@ -217,7 +217,7 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
sx={dataGridStyles} sx={dataGridStyles}
className={'datagrid--table'} className={'datagrid--table'}
getRowClassName={() => `datagrid--row`} getRowClassName={() => `datagrid--row`}
rows={categories ?? []} rows={courses ?? []}
columns={columns} columns={columns}
initialState={{ initialState={{
pagination: { pagination: {
@ -442,9 +442,9 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
</CardBoxModal> </CardBoxModal>
{categories && Array.isArray(categories) && !showGrid && ( {courses && Array.isArray(courses) && !showGrid && (
<ListCategories <CardCourses
categories={categories} courses={courses}
loading={loading} loading={loading}
onDelete={handleDeleteModalAction} onDelete={handleDeleteModalAction}
currentPage={currentPage} currentPage={currentPage}
@ -473,4 +473,4 @@ const TableSampleCategories = ({ filterItems, setFilterItems, filters, showGrid
) )
} }
export default TableSampleCategories export default TableSampleCourses

View File

@ -0,0 +1,274 @@
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_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) => (
<ImageField
name={'Avatar'}
image={params?.row?.thumbnail}
className='w-24 h-24 mx-auto lg:w-6 lg:h-6'
/>
),
},
{
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 [
<div key={params?.row?.id}>
<ListActionsPopover
onDelete={onDelete}
itemId={params?.row?.id}
pathEdit={`/courses/courses-edit/?id=${params?.row?.id}`}
pathView={`/courses/courses-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>,
]
},
},
];
};

View File

@ -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 (
<div className={'p-4'}>
{loading && <LoadingSpinner />}
<ul
role='list'
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
>
{!loading && enrollments.map((item, index) => (
<li
key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
}`}
>
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/enrollments/enrollments-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.enrollment_code}
</Link>
<div className='ml-auto '>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/enrollments/enrollments-edit/?id=${item.id}`}
pathView={`/enrollments/enrollments-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</div>
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>EnrollmentCode</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.enrollment_code }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Student</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.usersOneListFormatter(item.student) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Course</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.coursesOneListFormatter(item.course) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>EnrolledAt</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.enrolled_at) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Status</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.status }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Progress(%)</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.progress_percent }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>CertificateFile</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium'>
{dataFormatter.filesFormatter(item.certificate).map(link => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</div>
</dd>
</div>
</dl>
</li>
))}
{!loading && enrollments.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</ul>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</div>
);
};
export default CardEnrollments;

View File

@ -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 (
<>
<div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />}
{!loading && enrollments.map((item) => (
<div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link
href={`/enrollments/enrollments-view/?id=${item.id}`}
className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
}
>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>EnrollmentCode</p>
<p className={'line-clamp-2'}>{ item.enrollment_code }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Student</p>
<p className={'line-clamp-2'}>{ dataFormatter.usersOneListFormatter(item.student) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Course</p>
<p className={'line-clamp-2'}>{ dataFormatter.coursesOneListFormatter(item.course) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>EnrolledAt</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.enrolled_at) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Status</p>
<p className={'line-clamp-2'}>{ item.status }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Progress(%)</p>
<p className={'line-clamp-2'}>{ item.progress_percent }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>CertificateFile</p>
{dataFormatter.filesFormatter(item.certificate).map(link => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</div>
</Link>
<ListActionsPopover
onDelete={onDelete}
itemId={item.id}
pathEdit={`/enrollments/enrollments-edit/?id=${item.id}`}
pathView={`/enrollments/enrollments-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission}
/>
</div>
</CardBox>
</div>
))}
{!loading && enrollments.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p>
</div>
)}
</div>
<div className={'flex items-center justify-center my-6'}>
<Pagination
currentPage={currentPage}
numPages={numPages}
setCurrentPage={onPageChange}
/>
</div>
</>
)
};
export default ListEnrollments

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton' import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal' import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox"; 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 { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
@ -12,7 +12,7 @@ import {
DataGrid, DataGrid,
GridColDef, GridColDef,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import {loadColumns} from "./configureOrdersCols"; import {loadColumns} from "./configureEnrollmentsCols";
import _ from 'lodash'; import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter' import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles"; import {dataGridStyles} from "../../styles";
@ -24,7 +24,7 @@ import axios from 'axios';
const perPage = 10 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 notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -46,7 +46,7 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) =
const [kanbanColumns, setKanbanColumns] = useState<Array<{id: string, label: string}> | null>(null); const [kanbanColumns, setKanbanColumns] = useState<Array<{id: string, label: string}> | null>(null);
const [kanbanFilters, setKanbanFilters] = useState(''); 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 { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -66,10 +66,10 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) =
}; };
useEffect(() => { useEffect(() => {
if (ordersNotify.showNotification) { if (enrollmentsNotify.showNotification) {
notify(ordersNotify.typeNotification, ordersNotify.textNotification); notify(enrollmentsNotify.typeNotification, enrollmentsNotify.textNotification);
} }
}, [ordersNotify.showNotification]); }, [enrollmentsNotify.showNotification]);
useEffect(() => { useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
@ -98,19 +98,13 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) =
setKanbanColumns([ 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: "dropped", label: "dropped" },
{ id: "Cancelled", label: "Cancelled" },
{ id: "Returned", label: "Returned" },
{ id: "Refunded", label: "Refunded" },
]); ]);
@ -214,7 +208,7 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) =
loadColumns( loadColumns(
handleDeleteModalAction, handleDeleteModalAction,
`orders`, `enrollments`,
currentUser, currentUser,
).then((newCols) => setColumns(newCols)); ).then((newCols) => setColumns(newCols));
}, [currentUser]); }, [currentUser]);
@ -252,7 +246,7 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) =
sx={dataGridStyles} sx={dataGridStyles}
className={'datagrid--table'} className={'datagrid--table'}
getRowClassName={() => `datagrid--row`} getRowClassName={() => `datagrid--row`}
rows={orders ?? []} rows={enrollments ?? []}
columns={columns} columns={columns}
initialState={{ initialState={{
pagination: { pagination: {
@ -481,8 +475,8 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) =
{!showGrid && kanbanColumns && ( {!showGrid && kanbanColumns && (
<KanbanBoard <KanbanBoard
columnFieldName={'status'} columnFieldName={'status'}
showFieldName={'order_number'} showFieldName={'enrollment_code'}
entityName={'orders'} entityName={'enrollments'}
filtersQuery={kanbanFilters} filtersQuery={kanbanFilters}
deleteThunk={deleteItem} deleteThunk={deleteItem}
updateThunk={update} updateThunk={update}
@ -510,4 +504,4 @@ const TableSampleOrders = ({ filterItems, setFilterItems, filters, showGrid }) =
) )
} }
export default TableSampleOrders export default TableSampleEnrollments

View File

@ -37,13 +37,13 @@ export const loadColumns = async (
} }
} }
const hasUpdatePermission = hasPermission(user, 'UPDATE_PRODUCTS') const hasUpdatePermission = hasPermission(user, 'UPDATE_ENROLLMENTS')
return [ return [
{ {
field: 'sku', field: 'enrollment_code',
headerName: 'SKU', headerName: 'EnrollmentCode',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -57,91 +57,8 @@ export const loadColumns = async (
}, },
{ {
field: 'name', field: 'student',
headerName: 'Name', headerName: 'Student',
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: 'price',
headerName: 'Price',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'stock',
headerName: 'Stock',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'number',
},
{
field: 'images',
headerName: 'Images',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: false,
sortable: false,
renderCell: (params: GridValueGetterParams) => (
<ImageField
name={'Avatar'}
image={params?.row?.images}
className='w-24 h-24 mx-auto lg:w-6 lg:h-6'
/>
),
},
{
field: 'category',
headerName: 'Category',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -155,12 +72,52 @@ export const loadColumns = async (
type: 'singleSelect', type: 'singleSelect',
getOptionValue: (value: any) => value?.id, getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label, getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('categories'), valueOptions: await callOptionsApi('users'),
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value, 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', field: 'status',
headerName: '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 => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</>
),
},
{ {
field: 'actions', field: 'actions',
type: 'actions', type: 'actions',
@ -189,8 +188,8 @@ export const loadColumns = async (
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={params?.row?.id} itemId={params?.row?.id}
pathEdit={`/products/products-edit/?id=${params?.row?.id}`} pathEdit={`/enrollments/enrollments-edit/?id=${params?.row?.id}`}
pathView={`/products/products-view/?id=${params?.row?.id}`} pathView={`/enrollments/enrollments-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}

View File

@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
orders: any[]; lessons: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -20,8 +20,8 @@ type Props = {
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
}; };
const CardOrders = ({ const CardLessons = ({
orders, lessons,
loading, loading,
onDelete, onDelete,
currentPage, currentPage,
@ -37,7 +37,7 @@ const CardOrders = ({
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser); const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ORDERS') const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_LESSONS')
return ( return (
@ -47,7 +47,7 @@ const CardOrders = ({
role='list' 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' 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) => (
<li <li
key={item.id} key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${ className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
@ -57,8 +57,8 @@ const CardOrders = ({
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}> <div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/orders/orders-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'> <Link href={`/lessons/lessons-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.order_number} {item.title}
</Link> </Link>
@ -66,8 +66,8 @@ const CardOrders = ({
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/orders/orders-edit/?id=${item.id}`} pathEdit={`/lessons/lessons-edit/?id=${item.id}`}
pathView={`/orders/orders-view/?id=${item.id}`} pathView={`/lessons/lessons-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -78,10 +78,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>OrderNumber</dt> <dt className=' text-gray-500 dark:text-dark-600'>Title</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.order_number } { item.title }
</div> </div>
</dd> </dd>
</div> </div>
@ -90,10 +90,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Customer</dt> <dt className=' text-gray-500 dark:text-dark-600'>Course</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.customersOneListFormatter(item.customer) } { dataFormatter.coursesOneListFormatter(item.course) }
</div> </div>
</dd> </dd>
</div> </div>
@ -102,10 +102,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Status</dt> <dt className=' text-gray-500 dark:text-dark-600'>Content</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.status } { item.content }
</div> </div>
</dd> </dd>
</div> </div>
@ -114,10 +114,17 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>TotalAmount</dt> <dt className=' text-gray-500 dark:text-dark-600'>VideoFiles</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium'>
{ item.total } {dataFormatter.filesFormatter(item.video_files).map(link => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</div> </div>
</dd> </dd>
</div> </div>
@ -126,10 +133,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>PlacedAt</dt> <dt className=' text-gray-500 dark:text-dark-600'>Order</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.placed_at) } { item.order }
</div> </div>
</dd> </dd>
</div> </div>
@ -138,10 +145,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ShippedAt</dt> <dt className=' text-gray-500 dark:text-dark-600'>Duration(minutes)</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.shipped_at) } { item.duration }
</div> </div>
</dd> </dd>
</div> </div>
@ -150,10 +157,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>DeliveryDate</dt> <dt className=' text-gray-500 dark:text-dark-600'>StartAt</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.delivery_date) } { dataFormatter.dateTimeFormatter(item.start_at) }
</div> </div>
</dd> </dd>
</div> </div>
@ -162,10 +169,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>PaymentStatus</dt> <dt className=' text-gray-500 dark:text-dark-600'>EndAt</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.payment_status } { dataFormatter.dateTimeFormatter(item.end_at) }
</div> </div>
</dd> </dd>
</div> </div>
@ -174,10 +181,10 @@ const CardOrders = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>ShippingAddress</dt> <dt className=' text-gray-500 dark:text-dark-600'>Published</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.shipping_address } { dataFormatter.booleanFormatter(item.is_published) }
</div> </div>
</dd> </dd>
</div> </div>
@ -187,7 +194,7 @@ const CardOrders = ({
</dl> </dl>
</li> </li>
))} ))}
{!loading && orders.length === 0 && ( {!loading && lessons.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -204,4 +211,4 @@ const CardOrders = ({
); );
}; };
export default CardOrders; export default CardLessons;

View File

@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
orders: any[]; lessons: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -21,10 +21,10 @@ type Props = {
onPageChange: (page: number) => void; 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 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 corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor); const bgColor = useAppSelector((state) => state.style.cardsColor);
@ -34,13 +34,13 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh
<> <>
<div className='relative overflow-x-auto p-4 space-y-4'> <div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{!loading && orders.map((item) => ( {!loading && lessons.map((item) => (
<div key={item.id}> <div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}> <CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}> <div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link <Link
href={`/orders/orders-view/?id=${item.id}`} href={`/lessons/lessons-view/?id=${item.id}`}
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
@ -48,72 +48,79 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>OrderNumber</p> <p className={'text-xs text-gray-500 '}>Title</p>
<p className={'line-clamp-2'}>{ item.order_number }</p> <p className={'line-clamp-2'}>{ item.title }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Customer</p> <p className={'text-xs text-gray-500 '}>Course</p>
<p className={'line-clamp-2'}>{ dataFormatter.customersOneListFormatter(item.customer) }</p> <p className={'line-clamp-2'}>{ dataFormatter.coursesOneListFormatter(item.course) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Status</p> <p className={'text-xs text-gray-500 '}>Content</p>
<p className={'line-clamp-2'}>{ item.status }</p> <p className={'line-clamp-2'}>{ item.content }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>TotalAmount</p> <p className={'text-xs text-gray-500 '}>VideoFiles</p>
<p className={'line-clamp-2'}>{ item.total }</p> {dataFormatter.filesFormatter(item.video_files).map(link => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>PlacedAt</p> <p className={'text-xs text-gray-500 '}>Order</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.placed_at) }</p> <p className={'line-clamp-2'}>{ item.order }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>ShippedAt</p> <p className={'text-xs text-gray-500 '}>Duration(minutes)</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.shipped_at) }</p> <p className={'line-clamp-2'}>{ item.duration }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>DeliveryDate</p> <p className={'text-xs text-gray-500 '}>StartAt</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.delivery_date) }</p> <p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.start_at) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>PaymentStatus</p> <p className={'text-xs text-gray-500 '}>EndAt</p>
<p className={'line-clamp-2'}>{ item.payment_status }</p> <p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.end_at) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>ShippingAddress</p> <p className={'text-xs text-gray-500 '}>Published</p>
<p className={'line-clamp-2'}>{ item.shipping_address }</p> <p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.is_published) }</p>
</div> </div>
@ -122,8 +129,8 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/orders/orders-edit/?id=${item.id}`} pathEdit={`/lessons/lessons-edit/?id=${item.id}`}
pathView={`/orders/orders-view/?id=${item.id}`} pathView={`/lessons/lessons-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -132,7 +139,7 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh
</CardBox> </CardBox>
</div> </div>
))} ))}
{!loading && orders.length === 0 && ( {!loading && lessons.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -149,4 +156,4 @@ const ListOrders = ({ orders, loading, onDelete, currentPage, numPages, onPageCh
) )
}; };
export default ListOrders export default ListLessons

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton' import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal' import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox"; 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 { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
@ -12,7 +12,7 @@ import {
DataGrid, DataGrid,
GridColDef, GridColDef,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import {loadColumns} from "./configureShipmentsCols"; import {loadColumns} from "./configureLessonsCols";
import _ from 'lodash'; import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter' import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles"; import {dataGridStyles} from "../../styles";
@ -24,7 +24,7 @@ import { SlotInfo } from 'react-big-calendar';
const perPage = 100 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 notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch(); 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 { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -63,10 +63,10 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid }
}; };
useEffect(() => { useEffect(() => {
if (shipmentsNotify.showNotification) { if (lessonsNotify.showNotification) {
notify(shipmentsNotify.typeNotification, shipmentsNotify.textNotification); notify(lessonsNotify.typeNotification, lessonsNotify.textNotification);
} }
}, [shipmentsNotify.showNotification]); }, [lessonsNotify.showNotification]);
useEffect(() => { useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
@ -93,7 +93,7 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid }
const handleCreateEventAction = ({ start, end }: SlotInfo) => { const handleCreateEventAction = ({ start, end }: SlotInfo) => {
router.push( 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( loadColumns(
handleDeleteModalAction, handleDeleteModalAction,
`shipments`, `lessons`,
currentUser, currentUser,
).then((newCols) => setColumns(newCols)); ).then((newCols) => setColumns(newCols));
}, [currentUser]); }, [currentUser]);
@ -224,7 +224,7 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid }
sx={dataGridStyles} sx={dataGridStyles}
className={'datagrid--table'} className={'datagrid--table'}
getRowClassName={() => `datagrid--row`} getRowClassName={() => `datagrid--row`}
rows={shipments ?? []} rows={lessons ?? []}
columns={columns} columns={columns}
initialState={{ initialState={{
pagination: { pagination: {
@ -451,18 +451,18 @@ const TableSampleShipments = ({ filterItems, setFilterItems, filters, showGrid }
{!showGrid && ( {!showGrid && (
<BigCalendar <BigCalendar
events={shipments} events={lessons}
showField={'tracking_number'} showField={'title'}
start-data-key={'shipped_at'} start-data-key={'start_at'}
end-data-key={'delivered_at'} end-data-key={'end_at'}
handleDeleteAction={handleDeleteModalAction} handleDeleteAction={handleDeleteModalAction}
pathEdit={`/shipments/shipments-edit/?id=`} pathEdit={`/lessons/lessons-edit/?id=`}
pathView={`/shipments/shipments-view/?id=`} pathView={`/lessons/lessons-view/?id=`}
handleCreateEventAction={handleCreateEventAction} handleCreateEventAction={handleCreateEventAction}
onDateRangeChange={(range) => { onDateRangeChange={(range) => {
loadData(0,`&calendarStart=${range.start}&calendarEnd=${range.end}`); 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

View File

@ -37,13 +37,13 @@ export const loadColumns = async (
} }
} }
const hasUpdatePermission = hasPermission(user, 'UPDATE_ORDERS') const hasUpdatePermission = hasPermission(user, 'UPDATE_LESSONS')
return [ return [
{ {
field: 'order_number', field: 'title',
headerName: 'OrderNumber', headerName: 'Title',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -57,8 +57,8 @@ export const loadColumns = async (
}, },
{ {
field: 'customer', field: 'course',
headerName: 'Customer', headerName: 'Course',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -72,15 +72,15 @@ export const loadColumns = async (
type: 'singleSelect', type: 'singleSelect',
getOptionValue: (value: any) => value?.id, getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label, getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('customers'), valueOptions: await callOptionsApi('courses'),
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value, params?.value?.id ?? params?.value,
}, },
{ {
field: 'status', field: 'content',
headerName: 'Status', headerName: 'Content',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -94,8 +94,34 @@ export const loadColumns = async (
}, },
{ {
field: 'total', field: 'video_files',
headerName: 'TotalAmount', 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 => (
<button
key={link.publicUrl}
onClick={(e) => saveFile(e, link.publicUrl, link.name)}
>
{link.name}
</button>
))}
</>
),
},
{
field: 'order',
headerName: 'Order',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -110,8 +136,24 @@ export const loadColumns = async (
}, },
{ {
field: 'placed_at', field: 'duration',
headerName: 'PlacedAt', 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, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -123,13 +165,13 @@ export const loadColumns = async (
type: 'dateTime', type: 'dateTime',
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.placed_at), new Date(params.row.start_at),
}, },
{ {
field: 'shipped_at', field: 'end_at',
headerName: 'ShippedAt', headerName: 'EndAt',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -141,46 +183,13 @@ export const loadColumns = async (
type: 'dateTime', type: 'dateTime',
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.shipped_at), new Date(params.row.end_at),
}, },
{ {
field: 'delivery_date', field: 'is_published',
headerName: 'DeliveryDate', headerName: 'Published',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
type: 'dateTime',
valueGetter: (params: GridValueGetterParams) =>
new Date(params.row.delivery_date),
},
{
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, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -190,6 +199,7 @@ export const loadColumns = async (
editable: hasUpdatePermission, editable: hasUpdatePermission,
type: 'boolean',
}, },
@ -206,8 +216,8 @@ export const loadColumns = async (
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={params?.row?.id} itemId={params?.row?.id}
pathEdit={`/orders/orders-edit/?id=${params?.row?.id}`} pathEdit={`/lessons/lessons-edit/?id=${params?.row?.id}`}
pathView={`/orders/orders-view/?id=${params?.row?.id}`} pathView={`/lessons/lessons-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}

View File

@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
customers: any[]; progress: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -20,8 +20,8 @@ type Props = {
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
}; };
const CardCustomers = ({ const CardProgress = ({
customers, progress,
loading, loading,
onDelete, onDelete,
currentPage, currentPage,
@ -37,7 +37,7 @@ const CardCustomers = ({
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser); const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_CUSTOMERS') const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PROGRESS')
return ( return (
@ -47,7 +47,7 @@ const CardCustomers = ({
role='list' 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' 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) => (
<li <li
key={item.id} key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${ className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
@ -57,8 +57,8 @@ const CardCustomers = ({
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}> <div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/customers/customers-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'> <Link href={`/progress/progress-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.name} {item.summary}
</Link> </Link>
@ -66,8 +66,8 @@ const CardCustomers = ({
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/customers/customers-edit/?id=${item.id}`} pathEdit={`/progress/progress-edit/?id=${item.id}`}
pathView={`/customers/customers-view/?id=${item.id}`} pathView={`/progress/progress-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -78,10 +78,10 @@ const CardCustomers = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Name</dt> <dt className=' text-gray-500 dark:text-dark-600'>Summary</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.name } { item.summary }
</div> </div>
</dd> </dd>
</div> </div>
@ -90,10 +90,10 @@ const CardCustomers = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Email</dt> <dt className=' text-gray-500 dark:text-dark-600'>Student</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.email } { dataFormatter.usersOneListFormatter(item.student) }
</div> </div>
</dd> </dd>
</div> </div>
@ -102,10 +102,10 @@ const CardCustomers = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Phone</dt> <dt className=' text-gray-500 dark:text-dark-600'>Lesson</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.phone } { dataFormatter.lessonsOneListFormatter(item.lesson) }
</div> </div>
</dd> </dd>
</div> </div>
@ -114,10 +114,34 @@ const CardCustomers = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Address</dt> <dt className=' text-gray-500 dark:text-dark-600'>Completed</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.address } { dataFormatter.booleanFormatter(item.completed) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>CompletedAt</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.completed_at) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Percent</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.percent }
</div> </div>
</dd> </dd>
</div> </div>
@ -136,34 +160,10 @@ const CardCustomers = ({
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>VIP</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ dataFormatter.booleanFormatter(item.vip) }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>TaxNumber</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.tax_number }
</div>
</dd>
</div>
</dl> </dl>
</li> </li>
))} ))}
{!loading && customers.length === 0 && ( {!loading && progress.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -180,4 +180,4 @@ const CardCustomers = ({
); );
}; };
export default CardCustomers; export default CardProgress;

View File

@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
customers: any[]; progress: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -21,10 +21,10 @@ type Props = {
onPageChange: (page: number) => void; 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 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 corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor); const bgColor = useAppSelector((state) => state.style.cardsColor);
@ -34,13 +34,13 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on
<> <>
<div className='relative overflow-x-auto p-4 space-y-4'> <div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{!loading && customers.map((item) => ( {!loading && progress.map((item) => (
<div key={item.id}> <div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}> <CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}> <div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link <Link
href={`/customers/customers-view/?id=${item.id}`} href={`/progress/progress-view/?id=${item.id}`}
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
@ -48,32 +48,48 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Name</p> <p className={'text-xs text-gray-500 '}>Summary</p>
<p className={'line-clamp-2'}>{ item.name }</p> <p className={'line-clamp-2'}>{ item.summary }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Email</p> <p className={'text-xs text-gray-500 '}>Student</p>
<p className={'line-clamp-2'}>{ item.email }</p> <p className={'line-clamp-2'}>{ dataFormatter.usersOneListFormatter(item.student) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Phone</p> <p className={'text-xs text-gray-500 '}>Lesson</p>
<p className={'line-clamp-2'}>{ item.phone }</p> <p className={'line-clamp-2'}>{ dataFormatter.lessonsOneListFormatter(item.lesson) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Address</p> <p className={'text-xs text-gray-500 '}>Completed</p>
<p className={'line-clamp-2'}>{ item.address }</p> <p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.completed) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>CompletedAt</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.completed_at) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Percent</p>
<p className={'line-clamp-2'}>{ item.percent }</p>
</div> </div>
@ -86,28 +102,12 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>VIP</p>
<p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.vip) }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>TaxNumber</p>
<p className={'line-clamp-2'}>{ item.tax_number }</p>
</div>
</Link> </Link>
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/customers/customers-edit/?id=${item.id}`} pathEdit={`/progress/progress-edit/?id=${item.id}`}
pathView={`/customers/customers-view/?id=${item.id}`} pathView={`/progress/progress-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -116,7 +116,7 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on
</CardBox> </CardBox>
</div> </div>
))} ))}
{!loading && customers.length === 0 && ( {!loading && progress.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -133,4 +133,4 @@ const ListCustomers = ({ customers, loading, onDelete, currentPage, numPages, on
) )
}; };
export default ListCustomers export default ListProgress

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton' import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal' import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox"; 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 { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
@ -12,7 +12,7 @@ import {
DataGrid, DataGrid,
GridColDef, GridColDef,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import {loadColumns} from "./configureProductsCols"; import {loadColumns} from "./configureProgressCols";
import _ from 'lodash'; import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter' import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles"; import {dataGridStyles} from "../../styles";
@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles";
const perPage = 10 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 notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch(); 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 { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -60,10 +60,10 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
}; };
useEffect(() => { useEffect(() => {
if (productsNotify.showNotification) { if (progressNotify.showNotification) {
notify(productsNotify.typeNotification, productsNotify.textNotification); notify(progressNotify.typeNotification, progressNotify.textNotification);
} }
}, [productsNotify.showNotification]); }, [progressNotify.showNotification]);
useEffect(() => { useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
@ -177,7 +177,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
loadColumns( loadColumns(
handleDeleteModalAction, handleDeleteModalAction,
`products`, `progress`,
currentUser, currentUser,
).then((newCols) => setColumns(newCols)); ).then((newCols) => setColumns(newCols));
}, [currentUser]); }, [currentUser]);
@ -215,7 +215,7 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
sx={dataGridStyles} sx={dataGridStyles}
className={'datagrid--table'} className={'datagrid--table'}
getRowClassName={() => `datagrid--row`} getRowClassName={() => `datagrid--row`}
rows={products ?? []} rows={progress ?? []}
columns={columns} columns={columns}
initialState={{ initialState={{
pagination: { pagination: {
@ -460,4 +460,4 @@ const TableSampleProducts = ({ filterItems, setFilterItems, filters, showGrid })
) )
} }
export default TableSampleProducts export default TableSampleProgress

View File

@ -37,13 +37,13 @@ export const loadColumns = async (
} }
} }
const hasUpdatePermission = hasPermission(user, 'UPDATE_ORDER_ITEMS') const hasUpdatePermission = hasPermission(user, 'UPDATE_PROGRESS')
return [ return [
{ {
field: 'name', field: 'summary',
headerName: 'Name', headerName: 'Summary',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -57,8 +57,8 @@ export const loadColumns = async (
}, },
{ {
field: 'order', field: 'student',
headerName: 'Order', headerName: 'Student',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -72,15 +72,15 @@ export const loadColumns = async (
type: 'singleSelect', type: 'singleSelect',
getOptionValue: (value: any) => value?.id, getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label, getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('orders'), valueOptions: await callOptionsApi('users'),
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value, params?.value?.id ?? params?.value,
}, },
{ {
field: 'product', field: 'lesson',
headerName: 'Product', headerName: 'Lesson',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -94,15 +94,49 @@ export const loadColumns = async (
type: 'singleSelect', type: 'singleSelect',
getOptionValue: (value: any) => value?.id, getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label, getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('products'), valueOptions: await callOptionsApi('lessons'),
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value, params?.value?.id ?? params?.value,
}, },
{ {
field: 'quantity', field: 'completed',
headerName: 'Quantity', 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, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -117,8 +151,8 @@ export const loadColumns = async (
}, },
{ {
field: 'unit_price', field: 'notes',
headerName: 'UnitPrice', headerName: 'Notes',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -128,23 +162,6 @@ export const loadColumns = async (
editable: hasUpdatePermission, 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 (
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={params?.row?.id} itemId={params?.row?.id}
pathEdit={`/order_items/order_items-edit/?id=${params?.row?.id}`} pathEdit={`/progress/progress-edit/?id=${params?.row?.id}`}
pathView={`/order_items/order_items-view/?id=${params?.row?.id}`} pathView={`/progress/progress-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}

View File

@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
order_items: any[]; quiz_questions: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -20,8 +20,8 @@ type Props = {
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
}; };
const CardOrder_items = ({ const CardQuiz_questions = ({
order_items, quiz_questions,
loading, loading,
onDelete, onDelete,
currentPage, currentPage,
@ -37,7 +37,7 @@ const CardOrder_items = ({
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser); const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ORDER_ITEMS') const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_QUIZ_QUESTIONS')
return ( return (
@ -47,7 +47,7 @@ const CardOrder_items = ({
role='list' 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' 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) => (
<li <li
key={item.id} key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${ className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
@ -57,8 +57,8 @@ const CardOrder_items = ({
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}> <div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/order_items/order_items-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'> <Link href={`/quiz_questions/quiz_questions-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.name} {item.question}
</Link> </Link>
@ -66,8 +66,8 @@ const CardOrder_items = ({
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/order_items/order_items-edit/?id=${item.id}`} pathEdit={`/quiz_questions/quiz_questions-edit/?id=${item.id}`}
pathView={`/order_items/order_items-view/?id=${item.id}`} pathView={`/quiz_questions/quiz_questions-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -78,10 +78,10 @@ const CardOrder_items = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Name</dt> <dt className=' text-gray-500 dark:text-dark-600'>Quiz</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.name } { dataFormatter.quizzesOneListFormatter(item.quiz) }
</div> </div>
</dd> </dd>
</div> </div>
@ -90,10 +90,10 @@ const CardOrder_items = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Order</dt> <dt className=' text-gray-500 dark:text-dark-600'>Question</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.ordersOneListFormatter(item.order) } { item.question }
</div> </div>
</dd> </dd>
</div> </div>
@ -102,10 +102,10 @@ const CardOrder_items = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Product</dt> <dt className=' text-gray-500 dark:text-dark-600'>QuestionType</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.productsOneListFormatter(item.product) } { item.question_type }
</div> </div>
</dd> </dd>
</div> </div>
@ -114,10 +114,10 @@ const CardOrder_items = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Quantity</dt> <dt className=' text-gray-500 dark:text-dark-600'>Choices</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.quantity } { item.choices }
</div> </div>
</dd> </dd>
</div> </div>
@ -126,22 +126,10 @@ const CardOrder_items = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>UnitPrice</dt> <dt className=' text-gray-500 dark:text-dark-600'>Answer</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.unit_price } { item.answer }
</div>
</dd>
</div>
<div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>TotalPrice</dt>
<dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'>
{ item.total_price }
</div> </div>
</dd> </dd>
</div> </div>
@ -151,7 +139,7 @@ const CardOrder_items = ({
</dl> </dl>
</li> </li>
))} ))}
{!loading && order_items.length === 0 && ( {!loading && quiz_questions.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -168,4 +156,4 @@ const CardOrder_items = ({
); );
}; };
export default CardOrder_items; export default CardQuiz_questions;

View File

@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
shipments: any[]; quiz_questions: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -21,10 +21,10 @@ type Props = {
onPageChange: (page: number) => void; 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 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 corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor); const bgColor = useAppSelector((state) => state.style.cardsColor);
@ -34,13 +34,13 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on
<> <>
<div className='relative overflow-x-auto p-4 space-y-4'> <div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{!loading && shipments.map((item) => ( {!loading && quiz_questions.map((item) => (
<div key={item.id}> <div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}> <CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}> <div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link <Link
href={`/shipments/shipments-view/?id=${item.id}`} href={`/quiz_questions/quiz_questions-view/?id=${item.id}`}
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
@ -48,48 +48,40 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Order</p> <p className={'text-xs text-gray-500 '}>Quiz</p>
<p className={'line-clamp-2'}>{ dataFormatter.ordersOneListFormatter(item.order) }</p> <p className={'line-clamp-2'}>{ dataFormatter.quizzesOneListFormatter(item.quiz) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Carrier</p> <p className={'text-xs text-gray-500 '}>Question</p>
<p className={'line-clamp-2'}>{ item.carrier }</p> <p className={'line-clamp-2'}>{ item.question }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>TrackingNumber</p> <p className={'text-xs text-gray-500 '}>QuestionType</p>
<p className={'line-clamp-2'}>{ item.tracking_number }</p> <p className={'line-clamp-2'}>{ item.question_type }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>ShippedAt</p> <p className={'text-xs text-gray-500 '}>Choices</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.shipped_at) }</p> <p className={'line-clamp-2'}>{ item.choices }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>DeliveredAt</p> <p className={'text-xs text-gray-500 '}>Answer</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.delivered_at) }</p> <p className={'line-clamp-2'}>{ item.answer }</p>
</div>
<div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Status</p>
<p className={'line-clamp-2'}>{ item.status }</p>
</div> </div>
@ -98,8 +90,8 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/shipments/shipments-edit/?id=${item.id}`} pathEdit={`/quiz_questions/quiz_questions-edit/?id=${item.id}`}
pathView={`/shipments/shipments-view/?id=${item.id}`} pathView={`/quiz_questions/quiz_questions-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -108,7 +100,7 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on
</CardBox> </CardBox>
</div> </div>
))} ))}
{!loading && shipments.length === 0 && ( {!loading && quiz_questions.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -125,4 +117,4 @@ const ListShipments = ({ shipments, loading, onDelete, currentPage, numPages, on
) )
}; };
export default ListShipments export default ListQuiz_questions

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton' import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal' import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox"; 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 { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
@ -12,7 +12,7 @@ import {
DataGrid, DataGrid,
GridColDef, GridColDef,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import {loadColumns} from "./configurePaymentsCols"; import {loadColumns} from "./configureQuiz_questionsCols";
import _ from 'lodash'; import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter' import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles"; import {dataGridStyles} from "../../styles";
@ -21,7 +21,7 @@ import {dataGridStyles} from "../../styles";
const perPage = 10 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 notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch(); 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 { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -60,10 +60,10 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
}; };
useEffect(() => { useEffect(() => {
if (paymentsNotify.showNotification) { if (quiz_questionsNotify.showNotification) {
notify(paymentsNotify.typeNotification, paymentsNotify.textNotification); notify(quiz_questionsNotify.typeNotification, quiz_questionsNotify.textNotification);
} }
}, [paymentsNotify.showNotification]); }, [quiz_questionsNotify.showNotification]);
useEffect(() => { useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
@ -177,7 +177,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
loadColumns( loadColumns(
handleDeleteModalAction, handleDeleteModalAction,
`payments`, `quiz_questions`,
currentUser, currentUser,
).then((newCols) => setColumns(newCols)); ).then((newCols) => setColumns(newCols));
}, [currentUser]); }, [currentUser]);
@ -215,7 +215,7 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
sx={dataGridStyles} sx={dataGridStyles}
className={'datagrid--table'} className={'datagrid--table'}
getRowClassName={() => `datagrid--row`} getRowClassName={() => `datagrid--row`}
rows={payments ?? []} rows={quiz_questions ?? []}
columns={columns} columns={columns}
initialState={{ initialState={{
pagination: { pagination: {
@ -460,4 +460,4 @@ const TableSamplePayments = ({ filterItems, setFilterItems, filters, showGrid })
) )
} }
export default TableSamplePayments export default TableSampleQuiz_questions

View File

@ -37,13 +37,35 @@ export const loadColumns = async (
} }
} }
const hasUpdatePermission = hasPermission(user, 'UPDATE_CUSTOMERS') const hasUpdatePermission = hasPermission(user, 'UPDATE_QUIZ_QUESTIONS')
return [ return [
{ {
field: 'name', field: 'quiz',
headerName: 'Name', 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, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -57,8 +79,8 @@ export const loadColumns = async (
}, },
{ {
field: 'email', field: 'question_type',
headerName: 'Email', headerName: 'QuestionType',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -72,8 +94,8 @@ export const loadColumns = async (
}, },
{ {
field: 'phone', field: 'choices',
headerName: 'Phone', headerName: 'Choices',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -87,54 +109,8 @@ export const loadColumns = async (
}, },
{ {
field: 'address', field: 'answer',
headerName: 'Address', headerName: 'Answer',
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',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -160,8 +136,8 @@ export const loadColumns = async (
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={params?.row?.id} itemId={params?.row?.id}
pathEdit={`/customers/customers-edit/?id=${params?.row?.id}`} pathEdit={`/quiz_questions/quiz_questions-edit/?id=${params?.row?.id}`}
pathView={`/customers/customers-view/?id=${params?.row?.id}`} pathView={`/quiz_questions/quiz_questions-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}

View File

@ -12,7 +12,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
payments: any[]; quizzes: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -20,8 +20,8 @@ type Props = {
onPageChange: (page: number) => void; onPageChange: (page: number) => void;
}; };
const CardPayments = ({ const CardQuizzes = ({
payments, quizzes,
loading, loading,
onDelete, onDelete,
currentPage, currentPage,
@ -37,7 +37,7 @@ const CardPayments = ({
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const currentUser = useAppSelector((state) => state.auth.currentUser); const currentUser = useAppSelector((state) => state.auth.currentUser);
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAYMENTS') const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_QUIZZES')
return ( return (
@ -47,7 +47,7 @@ const CardPayments = ({
role='list' 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' 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) => (
<li <li
key={item.id} key={item.id}
className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${ className={`overflow-hidden ${corners !== 'rounded-full'? corners : 'rounded-3xl'} border ${focusRing} border-gray-200 dark:border-dark-700 ${
@ -57,8 +57,8 @@ const CardPayments = ({
<div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}> <div className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}>
<Link href={`/payments/payments-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'> <Link href={`/quizzes/quizzes-view/?id=${item.id}`} className='text-lg font-bold leading-6 line-clamp-1'>
{item.reference} {item.title}
</Link> </Link>
@ -66,8 +66,8 @@ const CardPayments = ({
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/payments/payments-edit/?id=${item.id}`} pathEdit={`/quizzes/quizzes-edit/?id=${item.id}`}
pathView={`/payments/payments-view/?id=${item.id}`} pathView={`/quizzes/quizzes-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -78,10 +78,10 @@ const CardPayments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Reference</dt> <dt className=' text-gray-500 dark:text-dark-600'>Title</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.reference } { item.title }
</div> </div>
</dd> </dd>
</div> </div>
@ -90,10 +90,10 @@ const CardPayments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Order</dt> <dt className=' text-gray-500 dark:text-dark-600'>Lesson</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.ordersOneListFormatter(item.order) } { dataFormatter.lessonsOneListFormatter(item.lesson) }
</div> </div>
</dd> </dd>
</div> </div>
@ -102,10 +102,10 @@ const CardPayments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Amount</dt> <dt className=' text-gray-500 dark:text-dark-600'>Description</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.amount } { item.description }
</div> </div>
</dd> </dd>
</div> </div>
@ -114,10 +114,10 @@ const CardPayments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Method</dt> <dt className=' text-gray-500 dark:text-dark-600'>PassingScore</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.method } { item.passing_score }
</div> </div>
</dd> </dd>
</div> </div>
@ -126,10 +126,10 @@ const CardPayments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>Status</dt> <dt className=' text-gray-500 dark:text-dark-600'>TimeLimit(minutes)</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ item.status } { item.time_limit }
</div> </div>
</dd> </dd>
</div> </div>
@ -138,10 +138,10 @@ const CardPayments = ({
<div className='flex justify-between gap-x-4 py-3'> <div className='flex justify-between gap-x-4 py-3'>
<dt className=' text-gray-500 dark:text-dark-600'>PaidAt</dt> <dt className=' text-gray-500 dark:text-dark-600'>Active</dt>
<dd className='flex items-start gap-x-2'> <dd className='flex items-start gap-x-2'>
<div className='font-medium line-clamp-4'> <div className='font-medium line-clamp-4'>
{ dataFormatter.dateTimeFormatter(item.paid_at) } { dataFormatter.booleanFormatter(item.is_active) }
</div> </div>
</dd> </dd>
</div> </div>
@ -151,7 +151,7 @@ const CardPayments = ({
</dl> </dl>
</li> </li>
))} ))}
{!loading && payments.length === 0 && ( {!loading && quizzes.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -168,4 +168,4 @@ const CardPayments = ({
); );
}; };
export default CardPayments; export default CardQuizzes;

View File

@ -13,7 +13,7 @@ import {hasPermission} from "../../helpers/userPermissions";
type Props = { type Props = {
payments: any[]; quizzes: any[];
loading: boolean; loading: boolean;
onDelete: (id: string) => void; onDelete: (id: string) => void;
currentPage: number; currentPage: number;
@ -21,10 +21,10 @@ type Props = {
onPageChange: (page: number) => void; 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 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 corners = useAppSelector((state) => state.style.corners);
const bgColor = useAppSelector((state) => state.style.cardsColor); const bgColor = useAppSelector((state) => state.style.cardsColor);
@ -34,13 +34,13 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa
<> <>
<div className='relative overflow-x-auto p-4 space-y-4'> <div className='relative overflow-x-auto p-4 space-y-4'>
{loading && <LoadingSpinner />} {loading && <LoadingSpinner />}
{!loading && payments.map((item) => ( {!loading && quizzes.map((item) => (
<div key={item.id}> <div key={item.id}>
<CardBox hasTable isList className={'rounded shadow-none'}> <CardBox hasTable isList className={'rounded shadow-none'}>
<div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}> <div className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}>
<Link <Link
href={`/payments/payments-view/?id=${item.id}`} href={`/quizzes/quizzes-view/?id=${item.id}`}
className={ className={
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto' 'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
} }
@ -48,48 +48,48 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Reference</p> <p className={'text-xs text-gray-500 '}>Title</p>
<p className={'line-clamp-2'}>{ item.reference }</p> <p className={'line-clamp-2'}>{ item.title }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Order</p> <p className={'text-xs text-gray-500 '}>Lesson</p>
<p className={'line-clamp-2'}>{ dataFormatter.ordersOneListFormatter(item.order) }</p> <p className={'line-clamp-2'}>{ dataFormatter.lessonsOneListFormatter(item.lesson) }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Amount</p> <p className={'text-xs text-gray-500 '}>Description</p>
<p className={'line-clamp-2'}>{ item.amount }</p> <p className={'line-clamp-2'}>{ item.description }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Method</p> <p className={'text-xs text-gray-500 '}>PassingScore</p>
<p className={'line-clamp-2'}>{ item.method }</p> <p className={'line-clamp-2'}>{ item.passing_score }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>Status</p> <p className={'text-xs text-gray-500 '}>TimeLimit(minutes)</p>
<p className={'line-clamp-2'}>{ item.status }</p> <p className={'line-clamp-2'}>{ item.time_limit }</p>
</div> </div>
<div className={'flex-1 px-3'}> <div className={'flex-1 px-3'}>
<p className={'text-xs text-gray-500 '}>PaidAt</p> <p className={'text-xs text-gray-500 '}>Active</p>
<p className={'line-clamp-2'}>{ dataFormatter.dateTimeFormatter(item.paid_at) }</p> <p className={'line-clamp-2'}>{ dataFormatter.booleanFormatter(item.is_active) }</p>
</div> </div>
@ -98,8 +98,8 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={item.id} itemId={item.id}
pathEdit={`/payments/payments-edit/?id=${item.id}`} pathEdit={`/quizzes/quizzes-edit/?id=${item.id}`}
pathView={`/payments/payments-view/?id=${item.id}`} pathView={`/quizzes/quizzes-view/?id=${item.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}
@ -108,7 +108,7 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa
</CardBox> </CardBox>
</div> </div>
))} ))}
{!loading && payments.length === 0 && ( {!loading && quizzes.length === 0 && (
<div className='col-span-full flex items-center justify-center h-40'> <div className='col-span-full flex items-center justify-center h-40'>
<p className=''>No data to display</p> <p className=''>No data to display</p>
</div> </div>
@ -125,4 +125,4 @@ const ListPayments = ({ payments, loading, onDelete, currentPage, numPages, onPa
) )
}; };
export default ListPayments export default ListQuizzes

View File

@ -4,7 +4,7 @@ import { ToastContainer, toast } from 'react-toastify';
import BaseButton from '../BaseButton' import BaseButton from '../BaseButton'
import CardBoxModal from '../CardBoxModal' import CardBoxModal from '../CardBoxModal'
import CardBox from "../CardBox"; 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 { useAppDispatch, useAppSelector } from '../../stores/hooks'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { Field, Form, Formik } from "formik"; import { Field, Form, Formik } from "formik";
@ -12,16 +12,18 @@ import {
DataGrid, DataGrid,
GridColDef, GridColDef,
} from '@mui/x-data-grid'; } from '@mui/x-data-grid';
import {loadColumns} from "./configureCustomersCols"; import {loadColumns} from "./configureQuizzesCols";
import _ from 'lodash'; import _ from 'lodash';
import dataFormatter from '../../helpers/dataFormatter' import dataFormatter from '../../helpers/dataFormatter'
import {dataGridStyles} from "../../styles"; import {dataGridStyles} from "../../styles";
import ListQuizzes from './ListQuizzes';
const perPage = 10 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 notify = (type, msg) => toast( msg, {type, position: "bottom-center"});
const dispatch = useAppDispatch(); 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 { currentUser } = useAppSelector((state) => state.auth);
const focusRing = useAppSelector((state) => state.style.focusRingColor); const focusRing = useAppSelector((state) => state.style.focusRingColor);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
@ -60,10 +62,10 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }
}; };
useEffect(() => { useEffect(() => {
if (customersNotify.showNotification) { if (quizzesNotify.showNotification) {
notify(customersNotify.typeNotification, customersNotify.textNotification); notify(quizzesNotify.typeNotification, quizzesNotify.textNotification);
} }
}, [customersNotify.showNotification]); }, [quizzesNotify.showNotification]);
useEffect(() => { useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
@ -177,7 +179,7 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }
loadColumns( loadColumns(
handleDeleteModalAction, handleDeleteModalAction,
`customers`, `quizzes`,
currentUser, currentUser,
).then((newCols) => setColumns(newCols)); ).then((newCols) => setColumns(newCols));
}, [currentUser]); }, [currentUser]);
@ -215,7 +217,7 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }
sx={dataGridStyles} sx={dataGridStyles}
className={'datagrid--table'} className={'datagrid--table'}
getRowClassName={() => `datagrid--row`} getRowClassName={() => `datagrid--row`}
rows={customers ?? []} rows={quizzes ?? []}
columns={columns} columns={columns}
initialState={{ initialState={{
pagination: { pagination: {
@ -440,10 +442,21 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }
</CardBoxModal> </CardBoxModal>
{dataGrid} {quizzes && Array.isArray(quizzes) && !showGrid && (
<ListQuizzes
quizzes={quizzes}
loading={loading}
onDelete={handleDeleteModalAction}
currentPage={currentPage}
numPages={numPages}
onPageChange={onPageChange}
/>
)}
{showGrid && dataGrid}
{selectedRows.length > 0 && {selectedRows.length > 0 &&
createPortal( createPortal(
@ -460,4 +473,4 @@ const TableSampleCustomers = ({ filterItems, setFilterItems, filters, showGrid }
) )
} }
export default TableSampleCustomers export default TableSampleQuizzes

View File

@ -37,13 +37,13 @@ export const loadColumns = async (
} }
} }
const hasUpdatePermission = hasPermission(user, 'UPDATE_PAYMENTS') const hasUpdatePermission = hasPermission(user, 'UPDATE_QUIZZES')
return [ return [
{ {
field: 'reference', field: 'title',
headerName: 'Reference', headerName: 'Title',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -57,8 +57,8 @@ export const loadColumns = async (
}, },
{ {
field: 'order', field: 'lesson',
headerName: 'Order', headerName: 'Lesson',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -72,15 +72,30 @@ export const loadColumns = async (
type: 'singleSelect', type: 'singleSelect',
getOptionValue: (value: any) => value?.id, getOptionValue: (value: any) => value?.id,
getOptionLabel: (value: any) => value?.label, getOptionLabel: (value: any) => value?.label,
valueOptions: await callOptionsApi('orders'), valueOptions: await callOptionsApi('lessons'),
valueGetter: (params: GridValueGetterParams) => valueGetter: (params: GridValueGetterParams) =>
params?.value?.id ?? params?.value, params?.value?.id ?? params?.value,
}, },
{ {
field: 'amount', field: 'description',
headerName: 'Amount', headerName: 'Description',
flex: 1,
minWidth: 120,
filterable: false,
headerClassName: 'datagrid--header',
cellClassName: 'datagrid--cell',
editable: hasUpdatePermission,
},
{
field: 'passing_score',
headerName: 'PassingScore',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -95,8 +110,8 @@ export const loadColumns = async (
}, },
{ {
field: 'method', field: 'time_limit',
headerName: 'Method', headerName: 'TimeLimit(minutes)',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -106,12 +121,13 @@ export const loadColumns = async (
editable: hasUpdatePermission, editable: hasUpdatePermission,
type: 'number',
}, },
{ {
field: 'status', field: 'is_active',
headerName: 'Status', headerName: 'Active',
flex: 1, flex: 1,
minWidth: 120, minWidth: 120,
filterable: false, filterable: false,
@ -121,24 +137,7 @@ export const loadColumns = async (
editable: hasUpdatePermission, editable: hasUpdatePermission,
type: 'boolean',
},
{
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),
}, },
@ -155,8 +154,8 @@ export const loadColumns = async (
<ListActionsPopover <ListActionsPopover
onDelete={onDelete} onDelete={onDelete}
itemId={params?.row?.id} itemId={params?.row?.id}
pathEdit={`/payments/payments-edit/?id=${params?.row?.id}`} pathEdit={`/quizzes/quizzes-edit/?id=${params?.row?.id}`}
pathView={`/payments/payments-view/?id=${params?.row?.id}`} pathView={`/quizzes/quizzes-view/?id=${params?.row?.id}`}
hasUpdatePermission={hasUpdatePermission} hasUpdatePermission={hasUpdatePermission}

View File

@ -40,6 +40,25 @@ export default {
}, },
usersManyListFormatter(val) {
if (!val || !val.length) return []
return val.map((item) => 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) { rolesManyListFormatter(val) {
@ -84,86 +103,90 @@ export default {
productsManyListFormatter(val) { course_categoriesManyListFormatter(val) {
if (!val || !val.length) return [] if (!val || !val.length) return []
return val.map((item) => item.name) return val.map((item) => item.name)
}, },
productsOneListFormatter(val) { course_categoriesOneListFormatter(val) {
if (!val) return '' if (!val) return ''
return val.name return val.name
}, },
productsManyListFormatterEdit(val) { course_categoriesManyListFormatterEdit(val) {
if (!val || !val.length) return [] if (!val || !val.length) return []
return val.map((item) => { return val.map((item) => {
return {id: item.id, label: item.name} return {id: item.id, label: item.name}
}); });
}, },
productsOneListFormatterEdit(val) { course_categoriesOneListFormatterEdit(val) {
if (!val) return '' if (!val) return ''
return {label: val.name, id: val.id} return {label: val.name, id: val.id}
}, },
categoriesManyListFormatter(val) { coursesManyListFormatter(val) {
if (!val || !val.length) return [] if (!val || !val.length) return []
return val.map((item) => item.name) return val.map((item) => item.title)
}, },
categoriesOneListFormatter(val) { coursesOneListFormatter(val) {
if (!val) return '' if (!val) return ''
return val.name return val.title
}, },
categoriesManyListFormatterEdit(val) { coursesManyListFormatterEdit(val) {
if (!val || !val.length) return [] if (!val || !val.length) return []
return val.map((item) => { 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 '' 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 [] if (!val || !val.length) return []
return val.map((item) => item.name) return val.map((item) => item.title)
}, },
customersOneListFormatter(val) { lessonsOneListFormatter(val) {
if (!val) return '' if (!val) return ''
return val.name return val.title
}, },
customersManyListFormatterEdit(val) { lessonsManyListFormatterEdit(val) {
if (!val || !val.length) return [] if (!val || !val.length) return []
return val.map((item) => { 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 '' 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 [] 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 '' if (!val) return ''
return val.order_number return val.title
}, },
ordersManyListFormatterEdit(val) { quizzesManyListFormatterEdit(val) {
if (!val || !val.length) return [] if (!val || !val.length) return []
return val.map((item) => { 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 '' if (!val) return ''
return {label: val.order_number, id: val.id} return {label: val.title, id: val.id}
}, },

View File

@ -7,6 +7,11 @@ const menuAside: MenuAsideItem[] = [
icon: icon.mdiViewDashboardOutline, icon: icon.mdiViewDashboardOutline,
label: 'Dashboard', label: 'Dashboard',
}, },
{
href: '/catalog',
icon: icon.mdiBookOpenPageVariant,
label: 'Catalog',
},
{ {
href: '/users/users-list', href: '/users/users-list',
@ -33,60 +38,76 @@ const menuAside: MenuAsideItem[] = [
permissions: 'READ_PERMISSIONS' permissions: 'READ_PERMISSIONS'
}, },
{ {
href: '/products/products-list', href: '/course_categories/course_categories-list',
label: 'Products', label: 'Course categories',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiPackage' in icon ? icon['mdiPackage' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiTag' in icon ? icon['mdiTag' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_PRODUCTS' permissions: 'READ_COURSE_CATEGORIES'
}, },
{ {
href: '/categories/categories-list', href: '/courses/courses-list',
label: 'Categories', label: 'Courses',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiShape' in icon ? icon['mdiShape' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiBookOpenVariant' in icon ? icon['mdiBookOpenVariant' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_CATEGORIES' permissions: 'READ_COURSES'
}, },
{ {
href: '/customers/customers-list', href: '/lessons/lessons-list',
label: 'Customers', label: 'Lessons',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiAccountGroup' in icon ? icon['mdiAccountGroup' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiPlayCircle' in icon ? icon['mdiPlayCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_CUSTOMERS' permissions: 'READ_LESSONS'
}, },
{ {
href: '/orders/orders-list', href: '/enrollments/enrollments-list',
label: 'Orders', label: 'Enrollments',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiCart' in icon ? icon['mdiCart' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiSchool' in icon ? icon['mdiSchool' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ORDERS' permissions: 'READ_ENROLLMENTS'
}, },
{ {
href: '/order_items/order_items-list', href: '/progress/progress-list',
label: 'Order items', label: 'Progress',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiCheckCircle' in icon ? icon['mdiCheckCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_ORDER_ITEMS' permissions: 'READ_PROGRESS'
}, },
{ {
href: '/payments/payments-list', href: '/quizzes/quizzes-list',
label: 'Payments', label: 'Quizzes',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiCreditCard' in icon ? icon['mdiCreditCard' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiClipboardList' in icon ? icon['mdiClipboardList' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_PAYMENTS' permissions: 'READ_QUIZZES'
}, },
{ {
href: '/shipments/shipments-list', href: '/quiz_questions/quiz_questions-list',
label: 'Shipments', label: 'Quiz questions',
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
icon: 'mdiTruck' in icon ? icon['mdiTruck' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable, icon: 'mdiHelpCircle' in icon ? icon['mdiHelpCircle' as keyof typeof icon] : icon.mdiTable ?? icon.mdiTable,
permissions: 'READ_SHIPMENTS' 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', href: '/profile',

View File

@ -149,10 +149,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
setStepsEnabled(false); setStepsEnabled(false);
}; };
const title = 'Store Operations Manager' const title = 'CourseFlow LMS'
const description = "Manage products, customers, orders, payments, and fulfillment workflows for retail operations." const description = "CourseFlow LMS: instructor and student workflows for courses, lessons, enrollments, and progress tracking."
const url = "https://flatlogic.com/" 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 imageWidth = '1920'
const imageHeight = '960' const imageHeight = '960'

Some files were not shown because too many files have changed in this diff Show More