Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5accde0a96 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
||||
node_modules/
|
||||
*/node_modules/
|
||||
*/build/
|
||||
|
||||
**/node_modules/
|
||||
**/build/
|
||||
.DS_Store
|
||||
.env
|
||||
File diff suppressed because one or more lines are too long
@ -1,366 +0,0 @@
|
||||
const db = require('../models');
|
||||
const FileDBApi = require('./file');
|
||||
const crypto = require('crypto');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class AnalyticsDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const analytics = await db.analytics.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
|
||||
student_engagement: data.student_engagement || null,
|
||||
completion_rate: data.completion_rate || null,
|
||||
instructor_performance: data.instructor_performance || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await analytics.setCourse(data.course || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
return analytics;
|
||||
}
|
||||
|
||||
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 analyticsData = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
|
||||
student_engagement: item.student_engagement || null,
|
||||
completion_rate: item.completion_rate || null,
|
||||
instructor_performance: item.instructor_performance || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
// Bulk create items
|
||||
const analytics = await db.analytics.bulkCreate(analyticsData, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
// For each item created, replace relation files
|
||||
|
||||
return analytics;
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const analytics = await db.analytics.findByPk(id, {}, { transaction });
|
||||
|
||||
const updatePayload = {};
|
||||
|
||||
if (data.student_engagement !== undefined)
|
||||
updatePayload.student_engagement = data.student_engagement;
|
||||
|
||||
if (data.completion_rate !== undefined)
|
||||
updatePayload.completion_rate = data.completion_rate;
|
||||
|
||||
if (data.instructor_performance !== undefined)
|
||||
updatePayload.instructor_performance = data.instructor_performance;
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await analytics.update(updatePayload, { transaction });
|
||||
|
||||
if (data.course !== undefined) {
|
||||
await analytics.setCourse(
|
||||
data.course,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
return analytics;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const analytics = await db.analytics.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of analytics) {
|
||||
await record.update({ deletedBy: currentUser.id }, { transaction });
|
||||
}
|
||||
for (const record of analytics) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
});
|
||||
|
||||
return analytics;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const analytics = await db.analytics.findByPk(id, options);
|
||||
|
||||
await analytics.update(
|
||||
{
|
||||
deletedBy: currentUser.id,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
await analytics.destroy({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return analytics;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const analytics = await db.analytics.findOne({ where }, { transaction });
|
||||
|
||||
if (!analytics) {
|
||||
return analytics;
|
||||
}
|
||||
|
||||
const output = analytics.get({ plain: true });
|
||||
|
||||
output.course = await analytics.getCourse({
|
||||
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.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}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
|
||||
if (filter) {
|
||||
if (filter.id) {
|
||||
where = {
|
||||
...where,
|
||||
['id']: Utils.uuid(filter.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.student_engagementRange) {
|
||||
const [start, end] = filter.student_engagementRange;
|
||||
|
||||
if (start !== undefined && start !== null && start !== '') {
|
||||
where = {
|
||||
...where,
|
||||
student_engagement: {
|
||||
...where.student_engagement,
|
||||
[Op.gte]: start,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== null && end !== '') {
|
||||
where = {
|
||||
...where,
|
||||
student_engagement: {
|
||||
...where.student_engagement,
|
||||
[Op.lte]: end,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.completion_rateRange) {
|
||||
const [start, end] = filter.completion_rateRange;
|
||||
|
||||
if (start !== undefined && start !== null && start !== '') {
|
||||
where = {
|
||||
...where,
|
||||
completion_rate: {
|
||||
...where.completion_rate,
|
||||
[Op.gte]: start,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== null && end !== '') {
|
||||
where = {
|
||||
...where,
|
||||
completion_rate: {
|
||||
...where.completion_rate,
|
||||
[Op.lte]: end,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.instructor_performanceRange) {
|
||||
const [start, end] = filter.instructor_performanceRange;
|
||||
|
||||
if (start !== undefined && start !== null && start !== '') {
|
||||
where = {
|
||||
...where,
|
||||
instructor_performance: {
|
||||
...where.instructor_performance,
|
||||
[Op.gte]: start,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== null && end !== '') {
|
||||
where = {
|
||||
...where,
|
||||
instructor_performance: {
|
||||
...where.instructor_performance,
|
||||
[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.analytics.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('analytics', 'course', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.analytics.findAll({
|
||||
attributes: ['id', 'course'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
orderBy: [['course', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.course,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -6,16 +6,15 @@ const Utils = require('../utils');
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class InstructorsDBApi {
|
||||
module.exports = class CourseDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const instructors = await db.instructors.create(
|
||||
const course = await db.course.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
|
||||
qualifications: data.qualifications || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
@ -23,15 +22,7 @@ module.exports = class InstructorsDBApi {
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await instructors.setUser(data.user || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await instructors.setCourses(data.courses || [], {
|
||||
transaction,
|
||||
});
|
||||
|
||||
return instructors;
|
||||
return course;
|
||||
}
|
||||
|
||||
static async bulkImport(data, options) {
|
||||
@ -39,10 +30,9 @@ module.exports = class InstructorsDBApi {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
// Prepare data - wrapping individual data transformations in a map() method
|
||||
const instructorsData = data.map((item, index) => ({
|
||||
const courseData = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
|
||||
qualifications: item.qualifications || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
@ -50,50 +40,33 @@ module.exports = class InstructorsDBApi {
|
||||
}));
|
||||
|
||||
// Bulk create items
|
||||
const instructors = await db.instructors.bulkCreate(instructorsData, {
|
||||
transaction,
|
||||
});
|
||||
const course = await db.course.bulkCreate(courseData, { transaction });
|
||||
|
||||
// For each item created, replace relation files
|
||||
|
||||
return instructors;
|
||||
return course;
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const instructors = await db.instructors.findByPk(id, {}, { transaction });
|
||||
const course = await db.course.findByPk(id, {}, { transaction });
|
||||
|
||||
const updatePayload = {};
|
||||
|
||||
if (data.qualifications !== undefined)
|
||||
updatePayload.qualifications = data.qualifications;
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await instructors.update(updatePayload, { transaction });
|
||||
await course.update(updatePayload, { transaction });
|
||||
|
||||
if (data.user !== undefined) {
|
||||
await instructors.setUser(
|
||||
data.user,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
if (data.courses !== undefined) {
|
||||
await instructors.setCourses(data.courses, { transaction });
|
||||
}
|
||||
|
||||
return instructors;
|
||||
return course;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const instructors = await db.instructors.findAll({
|
||||
const course = await db.course.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
@ -103,24 +76,24 @@ module.exports = class InstructorsDBApi {
|
||||
});
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of instructors) {
|
||||
for (const record of course) {
|
||||
await record.update({ deletedBy: currentUser.id }, { transaction });
|
||||
}
|
||||
for (const record of instructors) {
|
||||
for (const record of course) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
});
|
||||
|
||||
return instructors;
|
||||
return course;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const instructors = await db.instructors.findByPk(id, options);
|
||||
const course = await db.course.findByPk(id, options);
|
||||
|
||||
await instructors.update(
|
||||
await course.update(
|
||||
{
|
||||
deletedBy: currentUser.id,
|
||||
},
|
||||
@ -129,34 +102,23 @@ module.exports = class InstructorsDBApi {
|
||||
},
|
||||
);
|
||||
|
||||
await instructors.destroy({
|
||||
await course.destroy({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return instructors;
|
||||
return course;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const instructors = await db.instructors.findOne(
|
||||
{ where },
|
||||
{ transaction },
|
||||
);
|
||||
const course = await db.course.findOne({ where }, { transaction });
|
||||
|
||||
if (!instructors) {
|
||||
return instructors;
|
||||
if (!course) {
|
||||
return course;
|
||||
}
|
||||
|
||||
const output = instructors.get({ plain: true });
|
||||
|
||||
output.user = await instructors.getUser({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.courses = await instructors.getCourses({
|
||||
transaction,
|
||||
});
|
||||
const output = course.get({ plain: true });
|
||||
|
||||
return output;
|
||||
}
|
||||
@ -173,39 +135,7 @@ module.exports = class InstructorsDBApi {
|
||||
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
let include = [
|
||||
{
|
||||
model: db.users,
|
||||
as: 'user',
|
||||
|
||||
where: filter.user
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.user
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
|
||||
{
|
||||
model: db.courses,
|
||||
as: 'courses',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
let include = [];
|
||||
|
||||
if (filter) {
|
||||
if (filter.id) {
|
||||
@ -215,17 +145,6 @@ module.exports = class InstructorsDBApi {
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.qualifications) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike(
|
||||
'instructors',
|
||||
'qualifications',
|
||||
filter.qualifications,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.active !== undefined) {
|
||||
where = {
|
||||
...where,
|
||||
@ -233,38 +152,6 @@ module.exports = class InstructorsDBApi {
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.courses) {
|
||||
const searchTerms = filter.courses.split('|');
|
||||
|
||||
include = [
|
||||
{
|
||||
model: db.courses,
|
||||
as: 'courses_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
}
|
||||
|
||||
if (filter.createdAtRange) {
|
||||
const [start, end] = filter.createdAtRange;
|
||||
|
||||
@ -308,9 +195,7 @@ module.exports = class InstructorsDBApi {
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows, count } = await db.instructors.findAndCountAll(
|
||||
queryOptions,
|
||||
);
|
||||
const { rows, count } = await db.course.findAndCountAll(queryOptions);
|
||||
|
||||
return {
|
||||
rows: options?.countOnly ? [] : rows,
|
||||
@ -329,22 +214,22 @@ module.exports = class InstructorsDBApi {
|
||||
where = {
|
||||
[Op.or]: [
|
||||
{ ['id']: Utils.uuid(query) },
|
||||
Utils.ilike('instructors', 'user', query),
|
||||
Utils.ilike('course', 'id', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.instructors.findAll({
|
||||
attributes: ['id', 'user'],
|
||||
const records = await db.course.findAll({
|
||||
attributes: ['id', 'id'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
orderBy: [['user', 'ASC']],
|
||||
orderBy: [['id', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.user,
|
||||
label: record.id,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -1,428 +0,0 @@
|
||||
const db = require('../models');
|
||||
const FileDBApi = require('./file');
|
||||
const crypto = require('crypto');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class 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,
|
||||
description: data.description || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await courses.setInstructors(data.instructors || [], {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await courses.setStudents(data.students || [], {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await courses.setDiscussion_boards(data.discussion_boards || [], {
|
||||
transaction,
|
||||
});
|
||||
|
||||
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,
|
||||
description: item.description || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
// Bulk create items
|
||||
const courses = await db.courses.bulkCreate(coursesData, { transaction });
|
||||
|
||||
// For each item created, replace relation files
|
||||
|
||||
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.description !== undefined)
|
||||
updatePayload.description = data.description;
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await courses.update(updatePayload, { transaction });
|
||||
|
||||
if (data.instructors !== undefined) {
|
||||
await courses.setInstructors(data.instructors, { transaction });
|
||||
}
|
||||
|
||||
if (data.students !== undefined) {
|
||||
await courses.setStudents(data.students, { transaction });
|
||||
}
|
||||
|
||||
if (data.discussion_boards !== undefined) {
|
||||
await courses.setDiscussion_boards(data.discussion_boards, {
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
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.analytics_course = await courses.getAnalytics_course({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.discussion_boards_course = await courses.getDiscussion_boards_course(
|
||||
{
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
output.enrollments_course = await courses.getEnrollments_course({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.grades_course = await courses.getGrades_course({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.instructors = await courses.getInstructors({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.students = await courses.getStudents({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.discussion_boards = await courses.getDiscussion_boards({
|
||||
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: 'instructors',
|
||||
required: false,
|
||||
},
|
||||
|
||||
{
|
||||
model: db.users,
|
||||
as: 'students',
|
||||
required: false,
|
||||
},
|
||||
|
||||
{
|
||||
model: db.discussion_boards,
|
||||
as: 'discussion_boards',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
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.description) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('courses', 'description', filter.description),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.active !== undefined) {
|
||||
where = {
|
||||
...where,
|
||||
active: filter.active === true || filter.active === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.instructors) {
|
||||
const searchTerms = filter.instructors.split('|');
|
||||
|
||||
include = [
|
||||
{
|
||||
model: db.users,
|
||||
as: 'instructors_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
}
|
||||
|
||||
if (filter.students) {
|
||||
const searchTerms = filter.students.split('|');
|
||||
|
||||
include = [
|
||||
{
|
||||
model: db.users,
|
||||
as: 'students_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
}
|
||||
|
||||
if (filter.discussion_boards) {
|
||||
const searchTerms = filter.discussion_boards.split('|');
|
||||
|
||||
include = [
|
||||
{
|
||||
model: db.discussion_boards,
|
||||
as: 'discussion_boards_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
topic: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
}
|
||||
|
||||
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,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -1,355 +0,0 @@
|
||||
const db = require('../models');
|
||||
const FileDBApi = require('./file');
|
||||
const crypto = require('crypto');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class Discussion_boardsDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const discussion_boards = await db.discussion_boards.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
|
||||
topic: data.topic || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await discussion_boards.setCourse(data.course || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await discussion_boards.setPosts(data.posts || [], {
|
||||
transaction,
|
||||
});
|
||||
|
||||
return discussion_boards;
|
||||
}
|
||||
|
||||
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 discussion_boardsData = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
|
||||
topic: item.topic || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
// Bulk create items
|
||||
const discussion_boards = await db.discussion_boards.bulkCreate(
|
||||
discussion_boardsData,
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
// For each item created, replace relation files
|
||||
|
||||
return discussion_boards;
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const discussion_boards = await db.discussion_boards.findByPk(
|
||||
id,
|
||||
{},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
const updatePayload = {};
|
||||
|
||||
if (data.topic !== undefined) updatePayload.topic = data.topic;
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await discussion_boards.update(updatePayload, { transaction });
|
||||
|
||||
if (data.course !== undefined) {
|
||||
await discussion_boards.setCourse(
|
||||
data.course,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
if (data.posts !== undefined) {
|
||||
await discussion_boards.setPosts(data.posts, { transaction });
|
||||
}
|
||||
|
||||
return discussion_boards;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const discussion_boards = await db.discussion_boards.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of discussion_boards) {
|
||||
await record.update({ deletedBy: currentUser.id }, { transaction });
|
||||
}
|
||||
for (const record of discussion_boards) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
});
|
||||
|
||||
return discussion_boards;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const discussion_boards = await db.discussion_boards.findByPk(id, options);
|
||||
|
||||
await discussion_boards.update(
|
||||
{
|
||||
deletedBy: currentUser.id,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
await discussion_boards.destroy({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return discussion_boards;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const discussion_boards = await db.discussion_boards.findOne(
|
||||
{ where },
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
if (!discussion_boards) {
|
||||
return discussion_boards;
|
||||
}
|
||||
|
||||
const output = discussion_boards.get({ plain: true });
|
||||
|
||||
output.posts_discussion_board =
|
||||
await discussion_boards.getPosts_discussion_board({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.course = await discussion_boards.getCourse({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.posts = await discussion_boards.getPosts({
|
||||
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.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.posts,
|
||||
as: 'posts',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
if (filter) {
|
||||
if (filter.id) {
|
||||
where = {
|
||||
...where,
|
||||
['id']: Utils.uuid(filter.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.topic) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('discussion_boards', 'topic', filter.topic),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.active !== undefined) {
|
||||
where = {
|
||||
...where,
|
||||
active: filter.active === true || filter.active === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.posts) {
|
||||
const searchTerms = filter.posts.split('|');
|
||||
|
||||
include = [
|
||||
{
|
||||
model: db.posts,
|
||||
as: 'posts_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
content: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
}
|
||||
|
||||
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.discussion_boards.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('discussion_boards', 'topic', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.discussion_boards.findAll({
|
||||
attributes: ['id', 'topic'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
orderBy: [['topic', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.topic,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -6,17 +6,15 @@ const Utils = require('../utils');
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class PostsDBApi {
|
||||
module.exports = class Mcs_pyqDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const posts = await db.posts.create(
|
||||
const mcs_pyq = await db.mcs_pyq.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
|
||||
content: data.content || null,
|
||||
posted_at: data.posted_at || null,
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
@ -24,15 +22,7 @@ module.exports = class PostsDBApi {
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await posts.setDiscussion_board(data.discussion_board || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await posts.setUser(data.user || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
return posts;
|
||||
return mcs_pyq;
|
||||
}
|
||||
|
||||
static async bulkImport(data, options) {
|
||||
@ -40,11 +30,9 @@ module.exports = class PostsDBApi {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
// Prepare data - wrapping individual data transformations in a map() method
|
||||
const postsData = data.map((item, index) => ({
|
||||
const mcs_pyqData = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
|
||||
content: item.content || null,
|
||||
posted_at: item.posted_at || null,
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
@ -52,53 +40,33 @@ module.exports = class PostsDBApi {
|
||||
}));
|
||||
|
||||
// Bulk create items
|
||||
const posts = await db.posts.bulkCreate(postsData, { transaction });
|
||||
const mcs_pyq = await db.mcs_pyq.bulkCreate(mcs_pyqData, { transaction });
|
||||
|
||||
// For each item created, replace relation files
|
||||
|
||||
return posts;
|
||||
return mcs_pyq;
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const posts = await db.posts.findByPk(id, {}, { transaction });
|
||||
const mcs_pyq = await db.mcs_pyq.findByPk(id, {}, { transaction });
|
||||
|
||||
const updatePayload = {};
|
||||
|
||||
if (data.content !== undefined) updatePayload.content = data.content;
|
||||
|
||||
if (data.posted_at !== undefined) updatePayload.posted_at = data.posted_at;
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await posts.update(updatePayload, { transaction });
|
||||
await mcs_pyq.update(updatePayload, { transaction });
|
||||
|
||||
if (data.discussion_board !== undefined) {
|
||||
await posts.setDiscussion_board(
|
||||
data.discussion_board,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
if (data.user !== undefined) {
|
||||
await posts.setUser(
|
||||
data.user,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
return posts;
|
||||
return mcs_pyq;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const posts = await db.posts.findAll({
|
||||
const mcs_pyq = await db.mcs_pyq.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
@ -108,24 +76,24 @@ module.exports = class PostsDBApi {
|
||||
});
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of posts) {
|
||||
for (const record of mcs_pyq) {
|
||||
await record.update({ deletedBy: currentUser.id }, { transaction });
|
||||
}
|
||||
for (const record of posts) {
|
||||
for (const record of mcs_pyq) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
});
|
||||
|
||||
return posts;
|
||||
return mcs_pyq;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const posts = await db.posts.findByPk(id, options);
|
||||
const mcs_pyq = await db.mcs_pyq.findByPk(id, options);
|
||||
|
||||
await posts.update(
|
||||
await mcs_pyq.update(
|
||||
{
|
||||
deletedBy: currentUser.id,
|
||||
},
|
||||
@ -134,31 +102,23 @@ module.exports = class PostsDBApi {
|
||||
},
|
||||
);
|
||||
|
||||
await posts.destroy({
|
||||
await mcs_pyq.destroy({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return posts;
|
||||
return mcs_pyq;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const posts = await db.posts.findOne({ where }, { transaction });
|
||||
const mcs_pyq = await db.mcs_pyq.findOne({ where }, { transaction });
|
||||
|
||||
if (!posts) {
|
||||
return posts;
|
||||
if (!mcs_pyq) {
|
||||
return mcs_pyq;
|
||||
}
|
||||
|
||||
const output = posts.get({ plain: true });
|
||||
|
||||
output.discussion_board = await posts.getDiscussion_board({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.user = await posts.getUser({
|
||||
transaction,
|
||||
});
|
||||
const output = mcs_pyq.get({ plain: true });
|
||||
|
||||
return output;
|
||||
}
|
||||
@ -175,59 +135,7 @@ module.exports = class PostsDBApi {
|
||||
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
let include = [
|
||||
{
|
||||
model: db.discussion_boards,
|
||||
as: 'discussion_board',
|
||||
|
||||
where: filter.discussion_board
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.discussion_board
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
topic: {
|
||||
[Op.or]: filter.discussion_board
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
|
||||
{
|
||||
model: db.users,
|
||||
as: 'user',
|
||||
|
||||
where: filter.user
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.user
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
];
|
||||
let include = [];
|
||||
|
||||
if (filter) {
|
||||
if (filter.id) {
|
||||
@ -237,37 +145,6 @@ module.exports = class PostsDBApi {
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.content) {
|
||||
where = {
|
||||
...where,
|
||||
[Op.and]: Utils.ilike('posts', 'content', filter.content),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.posted_atRange) {
|
||||
const [start, end] = filter.posted_atRange;
|
||||
|
||||
if (start !== undefined && start !== null && start !== '') {
|
||||
where = {
|
||||
...where,
|
||||
posted_at: {
|
||||
...where.posted_at,
|
||||
[Op.gte]: start,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (end !== undefined && end !== null && end !== '') {
|
||||
where = {
|
||||
...where,
|
||||
posted_at: {
|
||||
...where.posted_at,
|
||||
[Op.lte]: end,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.active !== undefined) {
|
||||
where = {
|
||||
...where,
|
||||
@ -318,7 +195,7 @@ module.exports = class PostsDBApi {
|
||||
}
|
||||
|
||||
try {
|
||||
const { rows, count } = await db.posts.findAndCountAll(queryOptions);
|
||||
const { rows, count } = await db.mcs_pyq.findAndCountAll(queryOptions);
|
||||
|
||||
return {
|
||||
rows: options?.countOnly ? [] : rows,
|
||||
@ -337,22 +214,22 @@ module.exports = class PostsDBApi {
|
||||
where = {
|
||||
[Op.or]: [
|
||||
{ ['id']: Utils.uuid(query) },
|
||||
Utils.ilike('posts', 'content', query),
|
||||
Utils.ilike('mcs_pyq', 'id', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.posts.findAll({
|
||||
attributes: ['id', 'content'],
|
||||
const records = await db.mcs_pyq.findAll({
|
||||
attributes: ['id', 'id'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
orderBy: [['content', 'ASC']],
|
||||
orderBy: [['id', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.content,
|
||||
label: record.id,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -1,387 +0,0 @@
|
||||
const db = require('../models');
|
||||
const FileDBApi = require('./file');
|
||||
const crypto = require('crypto');
|
||||
const Utils = require('../utils');
|
||||
|
||||
const Sequelize = db.Sequelize;
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
module.exports = class StudentsDBApi {
|
||||
static async create(data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const students = await db.students.create(
|
||||
{
|
||||
id: data.id || undefined,
|
||||
|
||||
importHash: data.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await students.setUser(data.user || null, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await students.setEnrollments(data.enrollments || [], {
|
||||
transaction,
|
||||
});
|
||||
|
||||
await students.setGrades(data.grades || [], {
|
||||
transaction,
|
||||
});
|
||||
|
||||
return students;
|
||||
}
|
||||
|
||||
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 studentsData = data.map((item, index) => ({
|
||||
id: item.id || undefined,
|
||||
|
||||
importHash: item.importHash || null,
|
||||
createdById: currentUser.id,
|
||||
updatedById: currentUser.id,
|
||||
createdAt: new Date(Date.now() + index * 1000),
|
||||
}));
|
||||
|
||||
// Bulk create items
|
||||
const students = await db.students.bulkCreate(studentsData, {
|
||||
transaction,
|
||||
});
|
||||
|
||||
// For each item created, replace relation files
|
||||
|
||||
return students;
|
||||
}
|
||||
|
||||
static async update(id, data, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const students = await db.students.findByPk(id, {}, { transaction });
|
||||
|
||||
const updatePayload = {};
|
||||
|
||||
updatePayload.updatedById = currentUser.id;
|
||||
|
||||
await students.update(updatePayload, { transaction });
|
||||
|
||||
if (data.user !== undefined) {
|
||||
await students.setUser(
|
||||
data.user,
|
||||
|
||||
{ transaction },
|
||||
);
|
||||
}
|
||||
|
||||
if (data.enrollments !== undefined) {
|
||||
await students.setEnrollments(data.enrollments, { transaction });
|
||||
}
|
||||
|
||||
if (data.grades !== undefined) {
|
||||
await students.setGrades(data.grades, { transaction });
|
||||
}
|
||||
|
||||
return students;
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const students = await db.students.findAll({
|
||||
where: {
|
||||
id: {
|
||||
[Op.in]: ids,
|
||||
},
|
||||
},
|
||||
transaction,
|
||||
});
|
||||
|
||||
await db.sequelize.transaction(async (transaction) => {
|
||||
for (const record of students) {
|
||||
await record.update({ deletedBy: currentUser.id }, { transaction });
|
||||
}
|
||||
for (const record of students) {
|
||||
await record.destroy({ transaction });
|
||||
}
|
||||
});
|
||||
|
||||
return students;
|
||||
}
|
||||
|
||||
static async remove(id, options) {
|
||||
const currentUser = (options && options.currentUser) || { id: null };
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const students = await db.students.findByPk(id, options);
|
||||
|
||||
await students.update(
|
||||
{
|
||||
deletedBy: currentUser.id,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
await students.destroy({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return students;
|
||||
}
|
||||
|
||||
static async findBy(where, options) {
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
const students = await db.students.findOne({ where }, { transaction });
|
||||
|
||||
if (!students) {
|
||||
return students;
|
||||
}
|
||||
|
||||
const output = students.get({ plain: true });
|
||||
|
||||
output.enrollments_student = await students.getEnrollments_student({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.grades_student = await students.getGrades_student({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.user = await students.getUser({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.enrollments = await students.getEnrollments({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.grades = await students.getGrades({
|
||||
transaction,
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static async findAll(filter, options) {
|
||||
const limit = filter.limit || 0;
|
||||
let offset = 0;
|
||||
let where = {};
|
||||
const currentPage = +filter.page;
|
||||
|
||||
offset = currentPage * limit;
|
||||
|
||||
const orderBy = null;
|
||||
|
||||
const transaction = (options && options.transaction) || undefined;
|
||||
|
||||
let include = [
|
||||
{
|
||||
model: db.users,
|
||||
as: 'user',
|
||||
|
||||
where: filter.user
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: filter.user
|
||||
.split('|')
|
||||
.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
firstName: {
|
||||
[Op.or]: filter.user
|
||||
.split('|')
|
||||
.map((term) => ({ [Op.iLike]: `%${term}%` })),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {},
|
||||
},
|
||||
|
||||
{
|
||||
model: db.enrollments,
|
||||
as: 'enrollments',
|
||||
required: false,
|
||||
},
|
||||
|
||||
{
|
||||
model: db.grades,
|
||||
as: 'grades',
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
if (filter) {
|
||||
if (filter.id) {
|
||||
where = {
|
||||
...where,
|
||||
['id']: Utils.uuid(filter.id),
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.active !== undefined) {
|
||||
where = {
|
||||
...where,
|
||||
active: filter.active === true || filter.active === 'true',
|
||||
};
|
||||
}
|
||||
|
||||
if (filter.enrollments) {
|
||||
const searchTerms = filter.enrollments.split('|');
|
||||
|
||||
include = [
|
||||
{
|
||||
model: db.enrollments,
|
||||
as: 'enrollments_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
course: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
}
|
||||
|
||||
if (filter.grades) {
|
||||
const searchTerms = filter.grades.split('|');
|
||||
|
||||
include = [
|
||||
{
|
||||
model: db.grades,
|
||||
as: 'grades_filter',
|
||||
required: searchTerms.length > 0,
|
||||
where:
|
||||
searchTerms.length > 0
|
||||
? {
|
||||
[Op.or]: [
|
||||
{
|
||||
id: {
|
||||
[Op.in]: searchTerms.map((term) => Utils.uuid(term)),
|
||||
},
|
||||
},
|
||||
{
|
||||
grade: {
|
||||
[Op.or]: searchTerms.map((term) => ({
|
||||
[Op.iLike]: `%${term}%`,
|
||||
})),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
...include,
|
||||
];
|
||||
}
|
||||
|
||||
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.students.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('students', 'user', query),
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const records = await db.students.findAll({
|
||||
attributes: ['id', 'user'],
|
||||
where,
|
||||
limit: limit ? Number(limit) : undefined,
|
||||
offset: offset ? Number(offset) : undefined,
|
||||
orderBy: [['user', 'ASC']],
|
||||
});
|
||||
|
||||
return records.map((record) => ({
|
||||
id: record.id,
|
||||
label: record.user,
|
||||
}));
|
||||
}
|
||||
};
|
||||
@ -267,18 +267,6 @@ module.exports = class UsersDBApi {
|
||||
|
||||
const output = users.get({ plain: true });
|
||||
|
||||
output.instructors_user = await users.getInstructors_user({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.posts_user = await users.getPosts_user({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.students_user = await users.getStudents_user({
|
||||
transaction,
|
||||
});
|
||||
|
||||
output.avatar = await users.getAvatar({
|
||||
transaction,
|
||||
});
|
||||
|
||||
396
backend/src/db/migrations/1755629794386.js
Normal file
396
backend/src/db/migrations/1755629794386.js
Normal file
@ -0,0 +1,396 @@
|
||||
module.exports = {
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async up(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.createTable(
|
||||
'mcs_pyq',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'course',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.dropTable('courses', { transaction });
|
||||
|
||||
await queryInterface.dropTable('analytics', { transaction });
|
||||
|
||||
await queryInterface.dropTable('discussion_boards', { transaction });
|
||||
|
||||
await queryInterface.dropTable('enrollments', { transaction });
|
||||
|
||||
await queryInterface.dropTable('grades', { transaction });
|
||||
|
||||
await queryInterface.dropTable('instructors', { transaction });
|
||||
|
||||
await queryInterface.dropTable('posts', { transaction });
|
||||
|
||||
await queryInterface.dropTable('students', { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @param {QueryInterface} queryInterface
|
||||
* @param {Sequelize} Sequelize
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async down(queryInterface, Sequelize) {
|
||||
/**
|
||||
* @type {Transaction}
|
||||
*/
|
||||
const transaction = await queryInterface.sequelize.transaction();
|
||||
try {
|
||||
await queryInterface.dropTable('course', { transaction });
|
||||
|
||||
await queryInterface.dropTable('mcs_pyq', { transaction });
|
||||
|
||||
await queryInterface.createTable(
|
||||
'students',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'posts',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'instructors',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'grades',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'enrollments',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'discussion_boards',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'analytics',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await queryInterface.createTable(
|
||||
'courses',
|
||||
{
|
||||
id: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
defaultValue: Sequelize.DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
createdById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
updatedById: {
|
||||
type: Sequelize.DataTypes.UUID,
|
||||
references: {
|
||||
key: 'id',
|
||||
model: 'users',
|
||||
},
|
||||
},
|
||||
createdAt: { type: Sequelize.DataTypes.DATE },
|
||||
updatedAt: { type: Sequelize.DataTypes.DATE },
|
||||
deletedAt: { type: Sequelize.DataTypes.DATE },
|
||||
importHash: {
|
||||
type: Sequelize.DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const posts = sequelize.define(
|
||||
'posts',
|
||||
const course = sequelize.define(
|
||||
'course',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
@ -14,14 +14,6 @@ module.exports = function (sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
content: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
posted_at: {
|
||||
type: DataTypes.DATE,
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
@ -35,35 +27,19 @@ module.exports = function (sequelize, DataTypes) {
|
||||
},
|
||||
);
|
||||
|
||||
posts.associate = (db) => {
|
||||
course.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.posts.belongsTo(db.discussion_boards, {
|
||||
as: 'discussion_board',
|
||||
foreignKey: {
|
||||
name: 'discussion_boardId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.posts.belongsTo(db.users, {
|
||||
as: 'user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.posts.belongsTo(db.users, {
|
||||
db.course.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.posts.belongsTo(db.users, {
|
||||
db.course.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return posts;
|
||||
return course;
|
||||
};
|
||||
@ -1,139 +0,0 @@
|
||||
const config = require('../../config');
|
||||
const providers = config.providers;
|
||||
const crypto = require('crypto');
|
||||
const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const courses = sequelize.define(
|
||||
'courses',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
title: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
courses.associate = (db) => {
|
||||
db.courses.belongsToMany(db.users, {
|
||||
as: 'instructors',
|
||||
foreignKey: {
|
||||
name: 'courses_instructorsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'coursesInstructorsUsers',
|
||||
});
|
||||
|
||||
db.courses.belongsToMany(db.users, {
|
||||
as: 'instructors_filter',
|
||||
foreignKey: {
|
||||
name: 'courses_instructorsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'coursesInstructorsUsers',
|
||||
});
|
||||
|
||||
db.courses.belongsToMany(db.users, {
|
||||
as: 'students',
|
||||
foreignKey: {
|
||||
name: 'courses_studentsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'coursesStudentsUsers',
|
||||
});
|
||||
|
||||
db.courses.belongsToMany(db.users, {
|
||||
as: 'students_filter',
|
||||
foreignKey: {
|
||||
name: 'courses_studentsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'coursesStudentsUsers',
|
||||
});
|
||||
|
||||
db.courses.belongsToMany(db.discussion_boards, {
|
||||
as: 'discussion_boards',
|
||||
foreignKey: {
|
||||
name: 'courses_discussion_boardsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'coursesDiscussion_boardsDiscussion_boards',
|
||||
});
|
||||
|
||||
db.courses.belongsToMany(db.discussion_boards, {
|
||||
as: 'discussion_boards_filter',
|
||||
foreignKey: {
|
||||
name: 'courses_discussion_boardsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'coursesDiscussion_boardsDiscussion_boards',
|
||||
});
|
||||
|
||||
/// 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.analytics, {
|
||||
as: 'analytics_course',
|
||||
foreignKey: {
|
||||
name: 'courseId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.courses.hasMany(db.discussion_boards, {
|
||||
as: 'discussion_boards_course',
|
||||
foreignKey: {
|
||||
name: 'courseId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.courses.hasMany(db.enrollments, {
|
||||
as: 'enrollments_course',
|
||||
foreignKey: {
|
||||
name: 'courseId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.courses.hasMany(db.grades, {
|
||||
as: 'grades_course',
|
||||
foreignKey: {
|
||||
name: 'courseId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
//end loop
|
||||
|
||||
db.courses.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.courses.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return courses;
|
||||
};
|
||||
@ -1,83 +0,0 @@
|
||||
const config = require('../../config');
|
||||
const providers = config.providers;
|
||||
const crypto = require('crypto');
|
||||
const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const discussion_boards = sequelize.define(
|
||||
'discussion_boards',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
topic: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
discussion_boards.associate = (db) => {
|
||||
db.discussion_boards.belongsToMany(db.posts, {
|
||||
as: 'posts',
|
||||
foreignKey: {
|
||||
name: 'discussion_boards_postsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'discussion_boardsPostsPosts',
|
||||
});
|
||||
|
||||
db.discussion_boards.belongsToMany(db.posts, {
|
||||
as: 'posts_filter',
|
||||
foreignKey: {
|
||||
name: 'discussion_boards_postsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'discussion_boardsPostsPosts',
|
||||
});
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.discussion_boards.hasMany(db.posts, {
|
||||
as: 'posts_discussion_board',
|
||||
foreignKey: {
|
||||
name: 'discussion_boardId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
//end loop
|
||||
|
||||
db.discussion_boards.belongsTo(db.courses, {
|
||||
as: 'course',
|
||||
foreignKey: {
|
||||
name: 'courseId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.discussion_boards.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.discussion_boards.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return discussion_boards;
|
||||
};
|
||||
@ -1,75 +0,0 @@
|
||||
const config = require('../../config');
|
||||
const providers = config.providers;
|
||||
const crypto = require('crypto');
|
||||
const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const instructors = sequelize.define(
|
||||
'instructors',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
qualifications: {
|
||||
type: DataTypes.TEXT,
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
instructors.associate = (db) => {
|
||||
db.instructors.belongsToMany(db.courses, {
|
||||
as: 'courses',
|
||||
foreignKey: {
|
||||
name: 'instructors_coursesId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'instructorsCoursesCourses',
|
||||
});
|
||||
|
||||
db.instructors.belongsToMany(db.courses, {
|
||||
as: 'courses_filter',
|
||||
foreignKey: {
|
||||
name: 'instructors_coursesId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'instructorsCoursesCourses',
|
||||
});
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
//end loop
|
||||
|
||||
db.instructors.belongsTo(db.users, {
|
||||
as: 'user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.instructors.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.instructors.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return instructors;
|
||||
};
|
||||
@ -5,8 +5,8 @@ const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const enrollments = sequelize.define(
|
||||
'enrollments',
|
||||
const mcs_pyq = sequelize.define(
|
||||
'mcs_pyq',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
@ -14,12 +14,6 @@ module.exports = function (sequelize, DataTypes) {
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
payment_status: {
|
||||
type: DataTypes.ENUM,
|
||||
|
||||
values: ['pending', 'completed'],
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
@ -33,35 +27,19 @@ module.exports = function (sequelize, DataTypes) {
|
||||
},
|
||||
);
|
||||
|
||||
enrollments.associate = (db) => {
|
||||
mcs_pyq.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.enrollments.belongsTo(db.students, {
|
||||
as: 'student',
|
||||
foreignKey: {
|
||||
name: 'studentId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.enrollments.belongsTo(db.courses, {
|
||||
as: 'course',
|
||||
foreignKey: {
|
||||
name: 'courseId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.enrollments.belongsTo(db.users, {
|
||||
db.mcs_pyq.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.enrollments.belongsTo(db.users, {
|
||||
db.mcs_pyq.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return enrollments;
|
||||
return mcs_pyq;
|
||||
};
|
||||
@ -1,105 +0,0 @@
|
||||
const config = require('../../config');
|
||||
const providers = config.providers;
|
||||
const crypto = require('crypto');
|
||||
const bcrypt = require('bcrypt');
|
||||
const moment = require('moment');
|
||||
|
||||
module.exports = function (sequelize, DataTypes) {
|
||||
const students = sequelize.define(
|
||||
'students',
|
||||
{
|
||||
id: {
|
||||
type: DataTypes.UUID,
|
||||
defaultValue: DataTypes.UUIDV4,
|
||||
primaryKey: true,
|
||||
},
|
||||
|
||||
importHash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: true,
|
||||
unique: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
paranoid: true,
|
||||
freezeTableName: true,
|
||||
},
|
||||
);
|
||||
|
||||
students.associate = (db) => {
|
||||
db.students.belongsToMany(db.enrollments, {
|
||||
as: 'enrollments',
|
||||
foreignKey: {
|
||||
name: 'students_enrollmentsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'studentsEnrollmentsEnrollments',
|
||||
});
|
||||
|
||||
db.students.belongsToMany(db.enrollments, {
|
||||
as: 'enrollments_filter',
|
||||
foreignKey: {
|
||||
name: 'students_enrollmentsId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'studentsEnrollmentsEnrollments',
|
||||
});
|
||||
|
||||
db.students.belongsToMany(db.grades, {
|
||||
as: 'grades',
|
||||
foreignKey: {
|
||||
name: 'students_gradesId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'studentsGradesGrades',
|
||||
});
|
||||
|
||||
db.students.belongsToMany(db.grades, {
|
||||
as: 'grades_filter',
|
||||
foreignKey: {
|
||||
name: 'students_gradesId',
|
||||
},
|
||||
constraints: false,
|
||||
through: 'studentsGradesGrades',
|
||||
});
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.students.hasMany(db.enrollments, {
|
||||
as: 'enrollments_student',
|
||||
foreignKey: {
|
||||
name: 'studentId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.students.hasMany(db.grades, {
|
||||
as: 'grades_student',
|
||||
foreignKey: {
|
||||
name: 'studentId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
//end loop
|
||||
|
||||
db.students.belongsTo(db.users, {
|
||||
as: 'user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.students.belongsTo(db.users, {
|
||||
as: 'createdBy',
|
||||
});
|
||||
|
||||
db.students.belongsTo(db.users, {
|
||||
as: 'updatedBy',
|
||||
});
|
||||
};
|
||||
|
||||
return students;
|
||||
};
|
||||
@ -102,30 +102,6 @@ module.exports = function (sequelize, DataTypes) {
|
||||
|
||||
/// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
|
||||
|
||||
db.users.hasMany(db.instructors, {
|
||||
as: 'instructors_user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.users.hasMany(db.posts, {
|
||||
as: 'posts_user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
db.users.hasMany(db.students, {
|
||||
as: 'students_user',
|
||||
foreignKey: {
|
||||
name: 'userId',
|
||||
},
|
||||
constraints: false,
|
||||
});
|
||||
|
||||
//end loop
|
||||
|
||||
db.users.belongsTo(db.roles, {
|
||||
|
||||
@ -98,20 +98,7 @@ module.exports = {
|
||||
];
|
||||
}
|
||||
|
||||
const entities = [
|
||||
'users',
|
||||
'analytics',
|
||||
'courses',
|
||||
'discussion_boards',
|
||||
'enrollments',
|
||||
'grades',
|
||||
'instructors',
|
||||
'posts',
|
||||
'students',
|
||||
'roles',
|
||||
'permissions',
|
||||
,
|
||||
];
|
||||
const entities = ['users', 'roles', 'permissions', 'mcs_pyq', 'course', ,];
|
||||
await queryInterface.bulkInsert(
|
||||
'permissions',
|
||||
entities.flatMap(createPermissions),
|
||||
@ -221,678 +208,6 @@ primary key ("roles_permissionsId", "permissionId")
|
||||
permissionId: getId('READ_USERS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('CREATE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('READ_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('UPDATE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('EducationDirector'),
|
||||
permissionId: getId('DELETE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('CREATE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('READ_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('CourseManager'),
|
||||
permissionId: getId('UPDATE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('CREATE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('ContentSpecialist'),
|
||||
permissionId: getId('READ_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('CREATE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('TeachingAssistant'),
|
||||
permissionId: getId('READ_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Learner'),
|
||||
permissionId: getId('READ_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
@ -953,206 +268,6 @@ primary key ("roles_permissionsId", "permissionId")
|
||||
permissionId: getId('DELETE_USERS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_ANALYTICS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_ANALYTICS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_ANALYTICS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_ANALYTICS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_COURSES'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_COURSES'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_COURSES'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_COURSES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_DISCUSSION_BOARDS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_DISCUSSION_BOARDS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_ENROLLMENTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_ENROLLMENTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_ENROLLMENTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_ENROLLMENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_GRADES'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_GRADES'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_GRADES'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_GRADES'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_INSTRUCTORS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_INSTRUCTORS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_INSTRUCTORS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_INSTRUCTORS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_POSTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_POSTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_POSTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_POSTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_STUDENTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_STUDENTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_STUDENTS'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_STUDENTS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
@ -1203,6 +318,56 @@ primary key ("roles_permissionsId", "permissionId")
|
||||
permissionId: getId('DELETE_PERMISSIONS'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_MCS_PYQ'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_MCS_PYQ'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_MCS_PYQ'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_MCS_PYQ'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('CREATE_COURSE'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('READ_COURSE'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('UPDATE_COURSE'),
|
||||
},
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
roles_permissionsId: getId('Administrator'),
|
||||
permissionId: getId('DELETE_COURSE'),
|
||||
},
|
||||
|
||||
{
|
||||
createdAt,
|
||||
updatedAt,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
87
backend/src/db/seeders/20250819185634.js
Normal file
87
backend/src/db/seeders/20250819185634.js
Normal file
@ -0,0 +1,87 @@
|
||||
const { v4: uuid } = require('uuid');
|
||||
const db = require('../models');
|
||||
const Sequelize = require('sequelize');
|
||||
const config = require('../../config');
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @param{import("sequelize").QueryInterface} queryInterface
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async up(queryInterface) {
|
||||
const createdAt = new Date();
|
||||
const updatedAt = new Date();
|
||||
|
||||
/** @type {Map<string, string>} */
|
||||
const idMap = new Map();
|
||||
|
||||
/**
|
||||
* @param {string} key
|
||||
* @return {string}
|
||||
*/
|
||||
function getId(key) {
|
||||
if (idMap.has(key)) {
|
||||
return idMap.get(key);
|
||||
}
|
||||
const id = uuid();
|
||||
idMap.set(key, id);
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
function createPermissions(name) {
|
||||
return [
|
||||
{
|
||||
id: getId(`CREATE_${name.toUpperCase()}`),
|
||||
createdAt,
|
||||
updatedAt,
|
||||
name: `CREATE_${name.toUpperCase()}`,
|
||||
},
|
||||
{
|
||||
id: getId(`READ_${name.toUpperCase()}`),
|
||||
createdAt,
|
||||
updatedAt,
|
||||
name: `READ_${name.toUpperCase()}`,
|
||||
},
|
||||
{
|
||||
id: getId(`UPDATE_${name.toUpperCase()}`),
|
||||
createdAt,
|
||||
updatedAt,
|
||||
name: `UPDATE_${name.toUpperCase()}`,
|
||||
},
|
||||
{
|
||||
id: getId(`DELETE_${name.toUpperCase()}`),
|
||||
createdAt,
|
||||
updatedAt,
|
||||
name: `DELETE_${name.toUpperCase()}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const entities = ['mcs_pyq', 'course'];
|
||||
|
||||
const createdPermissions = entities.flatMap(createPermissions);
|
||||
|
||||
// Add permissions to database
|
||||
await queryInterface.bulkInsert('permissions', createdPermissions);
|
||||
// Get permissions ids
|
||||
const permissionsIds = createdPermissions.map((p) => p.id);
|
||||
// Get admin role
|
||||
const adminRole = await db.roles.findOne({
|
||||
where: { name: config.roles.admin },
|
||||
});
|
||||
|
||||
if (adminRole) {
|
||||
// Add permissions to admin role if it exists
|
||||
await adminRole.addPermissions(permissionsIds);
|
||||
}
|
||||
},
|
||||
down: async (queryInterface, Sequelize) => {
|
||||
await queryInterface.bulkDelete(
|
||||
'permissions',
|
||||
entities.flatMap(createPermissions),
|
||||
);
|
||||
},
|
||||
};
|
||||
@ -21,26 +21,14 @@ const contactFormRoutes = require('./routes/contactForm');
|
||||
|
||||
const usersRoutes = require('./routes/users');
|
||||
|
||||
const analyticsRoutes = require('./routes/analytics');
|
||||
|
||||
const coursesRoutes = require('./routes/courses');
|
||||
|
||||
const discussion_boardsRoutes = require('./routes/discussion_boards');
|
||||
|
||||
const enrollmentsRoutes = require('./routes/enrollments');
|
||||
|
||||
const gradesRoutes = require('./routes/grades');
|
||||
|
||||
const instructorsRoutes = require('./routes/instructors');
|
||||
|
||||
const postsRoutes = require('./routes/posts');
|
||||
|
||||
const studentsRoutes = require('./routes/students');
|
||||
|
||||
const rolesRoutes = require('./routes/roles');
|
||||
|
||||
const permissionsRoutes = require('./routes/permissions');
|
||||
|
||||
const mcs_pyqRoutes = require('./routes/mcs_pyq');
|
||||
|
||||
const courseRoutes = require('./routes/course');
|
||||
|
||||
const getBaseUrl = (url) => {
|
||||
if (!url) return '';
|
||||
return url.endsWith('/api') ? url.slice(0, -4) : url;
|
||||
@ -112,54 +100,6 @@ app.use(
|
||||
usersRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/analytics',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
analyticsRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/courses',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
coursesRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/discussion_boards',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
discussion_boardsRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/enrollments',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
enrollmentsRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/grades',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
gradesRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/instructors',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
instructorsRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/posts',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
postsRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/students',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
studentsRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/roles',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
@ -172,6 +112,18 @@ app.use(
|
||||
permissionsRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/mcs_pyq',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
mcs_pyqRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/course',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
courseRoutes,
|
||||
);
|
||||
|
||||
app.use(
|
||||
'/api/openai',
|
||||
passport.authenticate('jwt', { session: false }),
|
||||
|
||||
@ -1,449 +0,0 @@
|
||||
const express = require('express');
|
||||
|
||||
const AnalyticsService = require('../services/analytics');
|
||||
const AnalyticsDBApi = require('../db/api/analytics');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('analytics'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Analytics:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* student_engagement:
|
||||
* type: integer
|
||||
* format: int64
|
||||
* completion_rate:
|
||||
* type: integer
|
||||
* format: int64
|
||||
* instructor_performance:
|
||||
* type: integer
|
||||
* format: int64
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Analytics
|
||||
* description: The Analytics managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/analytics:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* 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/Analytics"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Analytics"
|
||||
* 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 AnalyticsService.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: [Analytics]
|
||||
* 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/Analytics"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Analytics"
|
||||
* 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 AnalyticsService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/analytics/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* 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/Analytics"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Analytics"
|
||||
* 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 AnalyticsService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/analytics/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* 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/Analytics"
|
||||
* 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 AnalyticsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/analytics/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* 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/Analytics"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await AnalyticsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/analytics:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* summary: Get all analytics
|
||||
* description: Get all analytics
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Analytics list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Analytics"
|
||||
* 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 AnalyticsDBApi.findAll(req.query, { currentUser });
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = [
|
||||
'id',
|
||||
'student_engagement',
|
||||
'completion_rate',
|
||||
'instructor_performance',
|
||||
];
|
||||
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/analytics/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* summary: Count all analytics
|
||||
* description: Count all analytics
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Analytics count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Analytics"
|
||||
* 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 AnalyticsDBApi.findAll(req.query, null, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/analytics/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* summary: Find all analytics that match search criteria
|
||||
* description: Find all analytics that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Analytics list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Analytics"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
const payload = await AnalyticsDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/analytics/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Analytics]
|
||||
* 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/Analytics"
|
||||
* 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 AnalyticsDBApi.findBy({ id: req.params.id });
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
|
||||
const PostsService = require('../services/posts');
|
||||
const PostsDBApi = require('../db/api/posts');
|
||||
const CourseService = require('../services/course');
|
||||
const CourseDBApi = require('../db/api/course');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
@ -10,36 +10,32 @@ const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('posts'));
|
||||
router.use(checkCrudPermissions('course'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Posts:
|
||||
* Course:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* content:
|
||||
* type: string
|
||||
* default: content
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Posts
|
||||
* description: The Posts managing API
|
||||
* name: Course
|
||||
* description: The Course managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts:
|
||||
* /api/course:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* tags: [Course]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
@ -51,14 +47,14 @@ router.use(checkCrudPermissions('posts'));
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
@ -73,7 +69,7 @@ router.post(
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await PostsService.create(req.body.data, req.currentUser, true, link.host);
|
||||
await CourseService.create(req.body.data, req.currentUser, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -85,7 +81,7 @@ router.post(
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* tags: [Course]
|
||||
* summary: Bulk import items
|
||||
* description: Bulk import items
|
||||
* requestBody:
|
||||
@ -98,14 +94,14 @@ router.post(
|
||||
* description: Data of the updated items
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
@ -121,7 +117,7 @@ router.post(
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await PostsService.bulkImport(req, res, true, link.host);
|
||||
await CourseService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -129,11 +125,11 @@ router.post(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts/{id}:
|
||||
* /api/course/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* tags: [Course]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
@ -156,7 +152,7 @@ router.post(
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
@ -165,7 +161,7 @@ router.post(
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
@ -178,7 +174,7 @@ router.post(
|
||||
router.put(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await PostsService.update(req.body.data, req.body.id, req.currentUser);
|
||||
await CourseService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -186,11 +182,11 @@ router.put(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts/{id}:
|
||||
* /api/course/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* tags: [Course]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
@ -206,7 +202,7 @@ router.put(
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
@ -219,7 +215,7 @@ router.put(
|
||||
router.delete(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await PostsService.remove(req.params.id, req.currentUser);
|
||||
await CourseService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -227,11 +223,11 @@ router.delete(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts/deleteByIds:
|
||||
* /api/course/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* tags: [Course]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
@ -249,7 +245,7 @@ router.delete(
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -260,7 +256,7 @@ router.delete(
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await PostsService.deleteByIds(req.body.data, req.currentUser);
|
||||
await CourseService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -268,22 +264,22 @@ router.post(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts:
|
||||
* /api/course:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* summary: Get all posts
|
||||
* description: Get all posts
|
||||
* tags: [Course]
|
||||
* summary: Get all course
|
||||
* description: Get all course
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Posts list successfully received
|
||||
* description: Course list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -297,9 +293,9 @@ router.get(
|
||||
const filetype = req.query.filetype;
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await PostsDBApi.findAll(req.query, { currentUser });
|
||||
const payload = await CourseDBApi.findAll(req.query, { currentUser });
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id', 'content', 'posted_at'];
|
||||
const fields = ['id'];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
@ -316,22 +312,22 @@ router.get(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts/count:
|
||||
* /api/course/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* summary: Count all posts
|
||||
* description: Count all posts
|
||||
* tags: [Course]
|
||||
* summary: Count all course
|
||||
* description: Count all course
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Posts count successfully received
|
||||
* description: Course count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -343,7 +339,7 @@ router.get(
|
||||
'/count',
|
||||
wrapAsync(async (req, res) => {
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await PostsDBApi.findAll(req.query, null, {
|
||||
const payload = await CourseDBApi.findAll(req.query, null, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
});
|
||||
@ -354,22 +350,22 @@ router.get(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts/autocomplete:
|
||||
* /api/course/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* summary: Find all posts that match search criteria
|
||||
* description: Find all posts that match search criteria
|
||||
* tags: [Course]
|
||||
* summary: Find all course that match search criteria
|
||||
* description: Find all course that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Posts list successfully received
|
||||
* description: Course list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -378,7 +374,7 @@ router.get(
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
const payload = await PostsDBApi.findAllAutocomplete(
|
||||
const payload = await CourseDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
@ -389,11 +385,11 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/posts/{id}:
|
||||
* /api/course/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Posts]
|
||||
* tags: [Course]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
@ -409,7 +405,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Posts"
|
||||
* $ref: "#/components/schemas/Course"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
@ -422,7 +418,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await PostsDBApi.findBy({ id: req.params.id });
|
||||
const payload = await CourseDBApi.findBy({ id: req.params.id });
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -1,444 +0,0 @@
|
||||
const express = require('express');
|
||||
|
||||
const Discussion_boardsService = require('../services/discussion_boards');
|
||||
const Discussion_boardsDBApi = require('../db/api/discussion_boards');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('discussion_boards'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Discussion_boards:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* topic:
|
||||
* type: string
|
||||
* default: topic
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Discussion_boards
|
||||
* description: The Discussion_boards managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/discussion_boards:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* 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/Discussion_boards"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Discussion_boards"
|
||||
* 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 Discussion_boardsService.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: [Discussion_boards]
|
||||
* 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/Discussion_boards"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Discussion_boards"
|
||||
* 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 Discussion_boardsService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/discussion_boards/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* 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/Discussion_boards"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Discussion_boards"
|
||||
* 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 Discussion_boardsService.update(
|
||||
req.body.data,
|
||||
req.body.id,
|
||||
req.currentUser,
|
||||
);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/discussion_boards/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* 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/Discussion_boards"
|
||||
* 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 Discussion_boardsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/discussion_boards/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* 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/Discussion_boards"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await Discussion_boardsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/discussion_boards:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* summary: Get all discussion_boards
|
||||
* description: Get all discussion_boards
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Discussion_boards list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Discussion_boards"
|
||||
* 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 Discussion_boardsDBApi.findAll(req.query, {
|
||||
currentUser,
|
||||
});
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id', 'topic'];
|
||||
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/discussion_boards/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* summary: Count all discussion_boards
|
||||
* description: Count all discussion_boards
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Discussion_boards count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Discussion_boards"
|
||||
* 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 Discussion_boardsDBApi.findAll(req.query, null, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/discussion_boards/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* summary: Find all discussion_boards that match search criteria
|
||||
* description: Find all discussion_boards that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Discussion_boards list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Discussion_boards"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
const payload = await Discussion_boardsDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/discussion_boards/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Discussion_boards]
|
||||
* 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/Discussion_boards"
|
||||
* 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 Discussion_boardsDBApi.findBy({ id: req.params.id });
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
@ -1,439 +0,0 @@
|
||||
const express = require('express');
|
||||
|
||||
const EnrollmentsService = require('../services/enrollments');
|
||||
const EnrollmentsDBApi = require('../db/api/enrollments');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('enrollments'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Enrollments:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Enrollments
|
||||
* description: The Enrollments managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/enrollments:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* 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/Enrollments"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Enrollments"
|
||||
* 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 EnrollmentsService.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: [Enrollments]
|
||||
* 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/Enrollments"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Enrollments"
|
||||
* 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 EnrollmentsService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/enrollments/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* 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/Enrollments"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Enrollments"
|
||||
* 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 EnrollmentsService.update(
|
||||
req.body.data,
|
||||
req.body.id,
|
||||
req.currentUser,
|
||||
);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/enrollments/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* 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/Enrollments"
|
||||
* 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 EnrollmentsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/enrollments/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* 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/Enrollments"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await EnrollmentsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/enrollments:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* summary: Get all enrollments
|
||||
* description: Get all enrollments
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Enrollments list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Enrollments"
|
||||
* 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 EnrollmentsDBApi.findAll(req.query, { currentUser });
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id'];
|
||||
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/enrollments/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* summary: Count all enrollments
|
||||
* description: Count all enrollments
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Enrollments count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Enrollments"
|
||||
* 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 EnrollmentsDBApi.findAll(req.query, null, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/enrollments/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* summary: Find all enrollments that match search criteria
|
||||
* description: Find all enrollments that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Enrollments list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Enrollments"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
const payload = await EnrollmentsDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/enrollments/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Enrollments]
|
||||
* 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/Enrollments"
|
||||
* 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 EnrollmentsDBApi.findBy({ id: req.params.id });
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
@ -1,442 +0,0 @@
|
||||
const express = require('express');
|
||||
|
||||
const InstructorsService = require('../services/instructors');
|
||||
const InstructorsDBApi = require('../db/api/instructors');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('instructors'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Instructors:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* qualifications:
|
||||
* type: string
|
||||
* default: qualifications
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Instructors
|
||||
* description: The Instructors managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/instructors:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* 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/Instructors"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Instructors"
|
||||
* 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 InstructorsService.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: [Instructors]
|
||||
* 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/Instructors"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Instructors"
|
||||
* 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 InstructorsService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/instructors/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* 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/Instructors"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item data was successfully updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Instructors"
|
||||
* 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 InstructorsService.update(
|
||||
req.body.data,
|
||||
req.body.id,
|
||||
req.currentUser,
|
||||
);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/instructors/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* 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/Instructors"
|
||||
* 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 InstructorsService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/instructors/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* 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/Instructors"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Items not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await InstructorsService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/instructors:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* summary: Get all instructors
|
||||
* description: Get all instructors
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Instructors list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Instructors"
|
||||
* 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 InstructorsDBApi.findAll(req.query, { currentUser });
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id', 'qualifications'];
|
||||
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/instructors/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* summary: Count all instructors
|
||||
* description: Count all instructors
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Instructors count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Instructors"
|
||||
* 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 InstructorsDBApi.findAll(req.query, null, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
});
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/instructors/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* summary: Find all instructors that match search criteria
|
||||
* description: Find all instructors that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Instructors list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Instructors"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
* description: Data not found
|
||||
* 500:
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
const payload = await InstructorsDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
);
|
||||
|
||||
res.status(200).send(payload);
|
||||
});
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/instructors/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Instructors]
|
||||
* 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/Instructors"
|
||||
* 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 InstructorsDBApi.findBy({ id: req.params.id });
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
);
|
||||
|
||||
router.use('/', require('../helpers').commonErrorHandler);
|
||||
|
||||
module.exports = router;
|
||||
@ -1,7 +1,7 @@
|
||||
const express = require('express');
|
||||
|
||||
const CoursesService = require('../services/courses');
|
||||
const CoursesDBApi = require('../db/api/courses');
|
||||
const Mcs_pyqService = require('../services/mcs_pyq');
|
||||
const Mcs_pyqDBApi = require('../db/api/mcs_pyq');
|
||||
const wrapAsync = require('../helpers').wrapAsync;
|
||||
|
||||
const router = express.Router();
|
||||
@ -10,39 +10,32 @@ const { parse } = require('json2csv');
|
||||
|
||||
const { checkCrudPermissions } = require('../middlewares/check-permissions');
|
||||
|
||||
router.use(checkCrudPermissions('courses'));
|
||||
router.use(checkCrudPermissions('mcs_pyq'));
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* components:
|
||||
* schemas:
|
||||
* Courses:
|
||||
* Mcs_pyq:
|
||||
* type: object
|
||||
* properties:
|
||||
|
||||
* title:
|
||||
* type: string
|
||||
* default: title
|
||||
* description:
|
||||
* type: string
|
||||
* default: description
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* tags:
|
||||
* name: Courses
|
||||
* description: The Courses managing API
|
||||
* name: Mcs_pyq
|
||||
* description: The Mcs_pyq managing API
|
||||
*/
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses:
|
||||
* /api/mcs_pyq:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Add new item
|
||||
* description: Add new item
|
||||
* requestBody:
|
||||
@ -54,14 +47,14 @@ router.use(checkCrudPermissions('courses'));
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The item was successfully added
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
@ -76,7 +69,7 @@ router.post(
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await CoursesService.create(
|
||||
await Mcs_pyqService.create(
|
||||
req.body.data,
|
||||
req.currentUser,
|
||||
true,
|
||||
@ -93,7 +86,7 @@ router.post(
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Bulk import items
|
||||
* description: Bulk import items
|
||||
* requestBody:
|
||||
@ -106,14 +99,14 @@ router.post(
|
||||
* description: Data of the updated items
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: The items were successfully imported
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 405:
|
||||
@ -129,7 +122,7 @@ router.post(
|
||||
req.headers.referer ||
|
||||
`${req.protocol}://${req.hostname}${req.originalUrl}`;
|
||||
const link = new URL(referer);
|
||||
await CoursesService.bulkImport(req, res, true, link.host);
|
||||
await Mcs_pyqService.bulkImport(req, res, true, link.host);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -137,11 +130,11 @@ router.post(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses/{id}:
|
||||
* /api/mcs_pyq/{id}:
|
||||
* put:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Update the data of the selected item
|
||||
* description: Update the data of the selected item
|
||||
* parameters:
|
||||
@ -164,7 +157,7 @@ router.post(
|
||||
* data:
|
||||
* description: Data of the updated item
|
||||
* type: object
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* required:
|
||||
* - id
|
||||
* responses:
|
||||
@ -173,7 +166,7 @@ router.post(
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
@ -186,7 +179,7 @@ router.post(
|
||||
router.put(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await CoursesService.update(req.body.data, req.body.id, req.currentUser);
|
||||
await Mcs_pyqService.update(req.body.data, req.body.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -194,11 +187,11 @@ router.put(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses/{id}:
|
||||
* /api/mcs_pyq/{id}:
|
||||
* delete:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Delete the selected item
|
||||
* description: Delete the selected item
|
||||
* parameters:
|
||||
@ -214,7 +207,7 @@ router.put(
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
@ -227,7 +220,7 @@ router.put(
|
||||
router.delete(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
await CoursesService.remove(req.params.id, req.currentUser);
|
||||
await Mcs_pyqService.remove(req.params.id, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -235,11 +228,11 @@ router.delete(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses/deleteByIds:
|
||||
* /api/mcs_pyq/deleteByIds:
|
||||
* post:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Delete the selected item list
|
||||
* description: Delete the selected item list
|
||||
* requestBody:
|
||||
@ -257,7 +250,7 @@ router.delete(
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -268,7 +261,7 @@ router.delete(
|
||||
router.post(
|
||||
'/deleteByIds',
|
||||
wrapAsync(async (req, res) => {
|
||||
await CoursesService.deleteByIds(req.body.data, req.currentUser);
|
||||
await Mcs_pyqService.deleteByIds(req.body.data, req.currentUser);
|
||||
const payload = true;
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -276,22 +269,22 @@ router.post(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses:
|
||||
* /api/mcs_pyq:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* summary: Get all courses
|
||||
* description: Get all courses
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Get all mcs_pyq
|
||||
* description: Get all mcs_pyq
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Courses list successfully received
|
||||
* description: Mcs_pyq list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -305,9 +298,9 @@ router.get(
|
||||
const filetype = req.query.filetype;
|
||||
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await CoursesDBApi.findAll(req.query, { currentUser });
|
||||
const payload = await Mcs_pyqDBApi.findAll(req.query, { currentUser });
|
||||
if (filetype && filetype === 'csv') {
|
||||
const fields = ['id', 'title', 'description'];
|
||||
const fields = ['id'];
|
||||
const opts = { fields };
|
||||
try {
|
||||
const csv = parse(payload.rows, opts);
|
||||
@ -324,22 +317,22 @@ router.get(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses/count:
|
||||
* /api/mcs_pyq/count:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* summary: Count all courses
|
||||
* description: Count all courses
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Count all mcs_pyq
|
||||
* description: Count all mcs_pyq
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Courses count successfully received
|
||||
* description: Mcs_pyq count successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -351,7 +344,7 @@ router.get(
|
||||
'/count',
|
||||
wrapAsync(async (req, res) => {
|
||||
const currentUser = req.currentUser;
|
||||
const payload = await CoursesDBApi.findAll(req.query, null, {
|
||||
const payload = await Mcs_pyqDBApi.findAll(req.query, null, {
|
||||
countOnly: true,
|
||||
currentUser,
|
||||
});
|
||||
@ -362,22 +355,22 @@ router.get(
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses/autocomplete:
|
||||
* /api/mcs_pyq/autocomplete:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* summary: Find all courses that match search criteria
|
||||
* description: Find all courses that match search criteria
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Find all mcs_pyq that match search criteria
|
||||
* description: Find all mcs_pyq that match search criteria
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Courses list successfully received
|
||||
* description: Mcs_pyq list successfully received
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 401:
|
||||
* $ref: "#/components/responses/UnauthorizedError"
|
||||
* 404:
|
||||
@ -386,7 +379,7 @@ router.get(
|
||||
* description: Some server error
|
||||
*/
|
||||
router.get('/autocomplete', async (req, res) => {
|
||||
const payload = await CoursesDBApi.findAllAutocomplete(
|
||||
const payload = await Mcs_pyqDBApi.findAllAutocomplete(
|
||||
req.query.query,
|
||||
req.query.limit,
|
||||
req.query.offset,
|
||||
@ -397,11 +390,11 @@ router.get('/autocomplete', async (req, res) => {
|
||||
|
||||
/**
|
||||
* @swagger
|
||||
* /api/courses/{id}:
|
||||
* /api/mcs_pyq/{id}:
|
||||
* get:
|
||||
* security:
|
||||
* - bearerAuth: []
|
||||
* tags: [Courses]
|
||||
* tags: [Mcs_pyq]
|
||||
* summary: Get selected item
|
||||
* description: Get selected item
|
||||
* parameters:
|
||||
@ -417,7 +410,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: "#/components/schemas/Courses"
|
||||
* $ref: "#/components/schemas/Mcs_pyq"
|
||||
* 400:
|
||||
* description: Invalid ID supplied
|
||||
* 401:
|
||||
@ -430,7 +423,7 @@ router.get('/autocomplete', async (req, res) => {
|
||||
router.get(
|
||||
'/:id',
|
||||
wrapAsync(async (req, res) => {
|
||||
const payload = await CoursesDBApi.findBy({ id: req.params.id });
|
||||
const payload = await Mcs_pyqDBApi.findBy({ id: req.params.id });
|
||||
|
||||
res.status(200).send(payload);
|
||||
}),
|
||||
@ -1,114 +0,0 @@
|
||||
const db = require('../db/models');
|
||||
const AnalyticsDBApi = require('../db/api/analytics');
|
||||
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 AnalyticsService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await AnalyticsDBApi.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 AnalyticsDBApi.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 analytics = await AnalyticsDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!analytics) {
|
||||
throw new ValidationError('analyticsNotFound');
|
||||
}
|
||||
|
||||
const updatedAnalytics = await AnalyticsDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updatedAnalytics;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await AnalyticsDBApi.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 AnalyticsDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
const db = require('../db/models');
|
||||
const PostsDBApi = require('../db/api/posts');
|
||||
const CourseDBApi = require('../db/api/course');
|
||||
const processFile = require('../middlewares/upload');
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
@ -7,11 +7,11 @@ const axios = require('axios');
|
||||
const config = require('../config');
|
||||
const stream = require('stream');
|
||||
|
||||
module.exports = class PostsService {
|
||||
module.exports = class CourseService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await PostsDBApi.create(data, {
|
||||
await CourseDBApi.create(data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
@ -44,7 +44,7 @@ module.exports = class PostsService {
|
||||
.on('error', (error) => reject(error));
|
||||
});
|
||||
|
||||
await PostsDBApi.bulkImport(results, {
|
||||
await CourseDBApi.bulkImport(results, {
|
||||
transaction,
|
||||
ignoreDuplicates: true,
|
||||
validate: true,
|
||||
@ -61,19 +61,19 @@ module.exports = class PostsService {
|
||||
static async update(data, id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
let posts = await PostsDBApi.findBy({ id }, { transaction });
|
||||
let course = await CourseDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!posts) {
|
||||
throw new ValidationError('postsNotFound');
|
||||
if (!course) {
|
||||
throw new ValidationError('courseNotFound');
|
||||
}
|
||||
|
||||
const updatedPosts = await PostsDBApi.update(id, data, {
|
||||
const updatedCourse = await CourseDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updatedPosts;
|
||||
return updatedCourse;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
@ -84,7 +84,7 @@ module.exports = class PostsService {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await PostsDBApi.deleteByIds(ids, {
|
||||
await CourseDBApi.deleteByIds(ids, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
@ -100,7 +100,7 @@ module.exports = class PostsService {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await PostsDBApi.remove(id, {
|
||||
await CourseDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
@ -1,121 +0,0 @@
|
||||
const db = require('../db/models');
|
||||
const Discussion_boardsDBApi = require('../db/api/discussion_boards');
|
||||
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 Discussion_boardsService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await Discussion_boardsDBApi.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 Discussion_boardsDBApi.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 discussion_boards = await Discussion_boardsDBApi.findBy(
|
||||
{ id },
|
||||
{ transaction },
|
||||
);
|
||||
|
||||
if (!discussion_boards) {
|
||||
throw new ValidationError('discussion_boardsNotFound');
|
||||
}
|
||||
|
||||
const updatedDiscussion_boards = await Discussion_boardsDBApi.update(
|
||||
id,
|
||||
data,
|
||||
{
|
||||
currentUser,
|
||||
transaction,
|
||||
},
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
return updatedDiscussion_boards;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await Discussion_boardsDBApi.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 Discussion_boardsDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,114 +0,0 @@
|
||||
const db = require('../db/models');
|
||||
const EnrollmentsDBApi = require('../db/api/enrollments');
|
||||
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 EnrollmentsService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await EnrollmentsDBApi.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 EnrollmentsDBApi.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 enrollments = await EnrollmentsDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!enrollments) {
|
||||
throw new ValidationError('enrollmentsNotFound');
|
||||
}
|
||||
|
||||
const updatedEnrollments = await EnrollmentsDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updatedEnrollments;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await EnrollmentsDBApi.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 EnrollmentsDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,114 +0,0 @@
|
||||
const db = require('../db/models');
|
||||
const InstructorsDBApi = require('../db/api/instructors');
|
||||
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 InstructorsService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await InstructorsDBApi.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 InstructorsDBApi.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 instructors = await InstructorsDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!instructors) {
|
||||
throw new ValidationError('instructorsNotFound');
|
||||
}
|
||||
|
||||
const updatedInstructors = await InstructorsDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updatedInstructors;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteByIds(ids, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await InstructorsDBApi.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 InstructorsDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1,5 +1,5 @@
|
||||
const db = require('../db/models');
|
||||
const StudentsDBApi = require('../db/api/students');
|
||||
const Mcs_pyqDBApi = require('../db/api/mcs_pyq');
|
||||
const processFile = require('../middlewares/upload');
|
||||
const ValidationError = require('./notifications/errors/validation');
|
||||
const csv = require('csv-parser');
|
||||
@ -7,11 +7,11 @@ const axios = require('axios');
|
||||
const config = require('../config');
|
||||
const stream = require('stream');
|
||||
|
||||
module.exports = class StudentsService {
|
||||
module.exports = class Mcs_pyqService {
|
||||
static async create(data, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
await StudentsDBApi.create(data, {
|
||||
await Mcs_pyqDBApi.create(data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
@ -44,7 +44,7 @@ module.exports = class StudentsService {
|
||||
.on('error', (error) => reject(error));
|
||||
});
|
||||
|
||||
await StudentsDBApi.bulkImport(results, {
|
||||
await Mcs_pyqDBApi.bulkImport(results, {
|
||||
transaction,
|
||||
ignoreDuplicates: true,
|
||||
validate: true,
|
||||
@ -61,19 +61,19 @@ module.exports = class StudentsService {
|
||||
static async update(data, id, currentUser) {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
try {
|
||||
let students = await StudentsDBApi.findBy({ id }, { transaction });
|
||||
let mcs_pyq = await Mcs_pyqDBApi.findBy({ id }, { transaction });
|
||||
|
||||
if (!students) {
|
||||
throw new ValidationError('studentsNotFound');
|
||||
if (!mcs_pyq) {
|
||||
throw new ValidationError('mcs_pyqNotFound');
|
||||
}
|
||||
|
||||
const updatedStudents = await StudentsDBApi.update(id, data, {
|
||||
const updatedMcs_pyq = await Mcs_pyqDBApi.update(id, data, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
|
||||
await transaction.commit();
|
||||
return updatedStudents;
|
||||
return updatedMcs_pyq;
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
@ -84,7 +84,7 @@ module.exports = class StudentsService {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await StudentsDBApi.deleteByIds(ids, {
|
||||
await Mcs_pyqDBApi.deleteByIds(ids, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
@ -100,7 +100,7 @@ module.exports = class StudentsService {
|
||||
const transaction = await db.sequelize.transaction();
|
||||
|
||||
try {
|
||||
await StudentsDBApi.remove(id, {
|
||||
await Mcs_pyqDBApi.remove(id, {
|
||||
currentUser,
|
||||
transaction,
|
||||
});
|
||||
@ -42,26 +42,8 @@ module.exports = class SearchService {
|
||||
}
|
||||
const tableColumns = {
|
||||
users: ['firstName', 'lastName', 'phoneNumber', 'email'],
|
||||
|
||||
courses: ['title', 'description'],
|
||||
|
||||
discussion_boards: ['topic'],
|
||||
|
||||
instructors: ['qualifications'],
|
||||
|
||||
posts: ['content'],
|
||||
};
|
||||
const columnsInt = {
|
||||
analytics: [
|
||||
'student_engagement',
|
||||
|
||||
'completion_rate',
|
||||
|
||||
'instructor_performance',
|
||||
],
|
||||
|
||||
grades: ['grade'],
|
||||
};
|
||||
const columnsInt = {};
|
||||
|
||||
let allFoundRecords = [];
|
||||
|
||||
|
||||
1
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -1,142 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImageField from '../ImageField';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { Pagination } from '../Pagination';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
analytics: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const CardAnalytics = ({
|
||||
analytics,
|
||||
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_ANALYTICS');
|
||||
|
||||
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 &&
|
||||
analytics.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={`/analytics/analytics-view/?id=${item.id}`}
|
||||
className='text-lg font-bold leading-6 line-clamp-1'
|
||||
>
|
||||
{item.course}
|
||||
</Link>
|
||||
|
||||
<div className='ml-auto '>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/analytics/analytics-edit/?id=${item.id}`}
|
||||
pathView={`/analytics/analytics-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'>
|
||||
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'>
|
||||
StudentEngagement
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.student_engagement}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
CompletionRate
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.completion_rate}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
InstructorPerformance
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.instructor_performance}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</li>
|
||||
))}
|
||||
{!loading && analytics.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 CardAnalytics;
|
||||
@ -1,116 +0,0 @@
|
||||
import React from 'react';
|
||||
import CardBox from '../CardBox';
|
||||
import ImageField from '../ImageField';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import { Pagination } from '../Pagination';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
analytics: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const ListAnalytics = ({
|
||||
analytics,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
numPages,
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ANALYTICS');
|
||||
|
||||
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 &&
|
||||
analytics.map((item) => (
|
||||
<div key={item.id}>
|
||||
<CardBox hasTable isList className={'rounded shadow-none'}>
|
||||
<div
|
||||
className={`flex ${bgColor} ${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||
>
|
||||
<Link
|
||||
href={`/analytics/analytics-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 '}>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 '}>
|
||||
StudentEngagement
|
||||
</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{item.student_engagement}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>
|
||||
CompletionRate
|
||||
</p>
|
||||
<p className={'line-clamp-2'}>{item.completion_rate}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>
|
||||
InstructorPerformance
|
||||
</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{item.instructor_performance}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/analytics/analytics-edit/?id=${item.id}`}
|
||||
pathView={`/analytics/analytics-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
))}
|
||||
{!loading && analytics.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 ListAnalytics;
|
||||
@ -1,124 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_ANALYTICS');
|
||||
|
||||
return [
|
||||
{
|
||||
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: 'student_engagement',
|
||||
headerName: 'StudentEngagement',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
type: 'number',
|
||||
},
|
||||
|
||||
{
|
||||
field: 'completion_rate',
|
||||
headerName: 'CompletionRate',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
type: 'number',
|
||||
},
|
||||
|
||||
{
|
||||
field: 'instructor_performance',
|
||||
headerName: 'InstructorPerformance',
|
||||
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={`/analytics/analytics-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/analytics/analytics-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -11,7 +11,7 @@ import Link from 'next/link';
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
instructors: any[];
|
||||
course: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
@ -19,8 +19,8 @@ type Props = {
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const CardInstructors = ({
|
||||
instructors,
|
||||
const CardCourse = ({
|
||||
course,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
@ -36,7 +36,7 @@ const CardInstructors = ({
|
||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_INSTRUCTORS');
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSE');
|
||||
|
||||
return (
|
||||
<div className={'p-4'}>
|
||||
@ -46,7 +46,7 @@ const CardInstructors = ({
|
||||
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
||||
>
|
||||
{!loading &&
|
||||
instructors.map((item, index) => (
|
||||
course.map((item, index) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className={`overflow-hidden ${
|
||||
@ -59,59 +59,26 @@ const CardInstructors = ({
|
||||
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={`/instructors/instructors-view/?id=${item.id}`}
|
||||
href={`/course/course-view/?id=${item.id}`}
|
||||
className='text-lg font-bold leading-6 line-clamp-1'
|
||||
>
|
||||
{item.user}
|
||||
{item.id}
|
||||
</Link>
|
||||
|
||||
<div className='ml-auto '>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/instructors/instructors-edit/?id=${item.id}`}
|
||||
pathView={`/instructors/instructors-view/?id=${item.id}`}
|
||||
pathEdit={`/course/course-edit/?id=${item.id}`}
|
||||
pathView={`/course/course-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'>User</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter.usersOneListFormatter(item.user)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Qualifications
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.qualifications}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Courses
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter
|
||||
.coursesManyListFormatter(item.courses)
|
||||
.join(', ')}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'></dl>
|
||||
</li>
|
||||
))}
|
||||
{!loading && instructors.length === 0 && (
|
||||
{!loading && course.length === 0 && (
|
||||
<div className='col-span-full flex items-center justify-center h-40'>
|
||||
<p className=''>No data to display</p>
|
||||
</div>
|
||||
@ -128,4 +95,4 @@ const CardInstructors = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default CardInstructors;
|
||||
export default CardCourse;
|
||||
@ -12,7 +12,7 @@ import Link from 'next/link';
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
instructors: any[];
|
||||
course: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
@ -20,8 +20,8 @@ type Props = {
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const ListInstructors = ({
|
||||
instructors,
|
||||
const ListCourse = ({
|
||||
course,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
@ -29,7 +29,7 @@ const ListInstructors = ({
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_INSTRUCTORS');
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSE');
|
||||
|
||||
const corners = useAppSelector((state) => state.style.corners);
|
||||
const bgColor = useAppSelector((state) => state.style.cardsColor);
|
||||
@ -39,7 +39,7 @@ const ListInstructors = ({
|
||||
<div className='relative overflow-x-auto p-4 space-y-4'>
|
||||
{loading && <LoadingSpinner />}
|
||||
{!loading &&
|
||||
instructors.map((item) => (
|
||||
course.map((item) => (
|
||||
<div key={item.id}>
|
||||
<CardBox hasTable isList className={'rounded shadow-none'}>
|
||||
<div
|
||||
@ -48,46 +48,23 @@ const ListInstructors = ({
|
||||
} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||
>
|
||||
<Link
|
||||
href={`/instructors/instructors-view/?id=${item.id}`}
|
||||
href={`/course/course-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 '}>User</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter.usersOneListFormatter(item.user)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>
|
||||
Qualifications
|
||||
</p>
|
||||
<p className={'line-clamp-2'}>{item.qualifications}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Courses</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter
|
||||
.coursesManyListFormatter(item.courses)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
></Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/instructors/instructors-edit/?id=${item.id}`}
|
||||
pathView={`/instructors/instructors-view/?id=${item.id}`}
|
||||
pathEdit={`/course/course-edit/?id=${item.id}`}
|
||||
pathView={`/course/course-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
))}
|
||||
{!loading && instructors.length === 0 && (
|
||||
{!loading && course.length === 0 && (
|
||||
<div className='col-span-full flex items-center justify-center h-40'>
|
||||
<p className=''>No data to display</p>
|
||||
</div>
|
||||
@ -104,4 +81,4 @@ const ListInstructors = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ListInstructors;
|
||||
export default ListCourse;
|
||||
@ -10,19 +10,19 @@ import {
|
||||
deleteItem,
|
||||
setRefetch,
|
||||
deleteItemsByIds,
|
||||
} from '../../stores/enrollments/enrollmentsSlice';
|
||||
} from '../../stores/course/courseSlice';
|
||||
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 './configureEnrollmentsCols';
|
||||
import { loadColumns } from './configureCourseCols';
|
||||
import _ from 'lodash';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { dataGridStyles } from '../../styles';
|
||||
|
||||
const perPage = 10;
|
||||
|
||||
const TableSampleEnrollments = ({
|
||||
const TableSampleCourse = ({
|
||||
filterItems,
|
||||
setFilterItems,
|
||||
filters,
|
||||
@ -47,12 +47,12 @@ const TableSampleEnrollments = ({
|
||||
]);
|
||||
|
||||
const {
|
||||
enrollments,
|
||||
course,
|
||||
loading,
|
||||
count,
|
||||
notify: enrollmentsNotify,
|
||||
notify: courseNotify,
|
||||
refetch,
|
||||
} = useAppSelector((state) => state.enrollments);
|
||||
} = useAppSelector((state) => state.course);
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
||||
@ -73,13 +73,10 @@ const TableSampleEnrollments = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (enrollmentsNotify.showNotification) {
|
||||
notify(
|
||||
enrollmentsNotify.typeNotification,
|
||||
enrollmentsNotify.textNotification,
|
||||
);
|
||||
if (courseNotify.showNotification) {
|
||||
notify(courseNotify.typeNotification, courseNotify.textNotification);
|
||||
}
|
||||
}, [enrollmentsNotify.showNotification]);
|
||||
}, [courseNotify.showNotification]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
@ -184,7 +181,7 @@ const TableSampleEnrollments = ({
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
|
||||
loadColumns(handleDeleteModalAction, `enrollments`, currentUser).then(
|
||||
loadColumns(handleDeleteModalAction, `course`, currentUser).then(
|
||||
(newCols) => setColumns(newCols),
|
||||
);
|
||||
}, [currentUser]);
|
||||
@ -218,7 +215,7 @@ const TableSampleEnrollments = ({
|
||||
sx={dataGridStyles}
|
||||
className={'datagrid--table'}
|
||||
getRowClassName={() => `datagrid--row`}
|
||||
rows={enrollments ?? []}
|
||||
rows={course ?? []}
|
||||
columns={columns}
|
||||
initialState={{
|
||||
pagination: {
|
||||
@ -484,4 +481,4 @@ const TableSampleEnrollments = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default TableSampleEnrollments;
|
||||
export default TableSampleCourse;
|
||||
62
frontend/src/components/Course/configureCourseCols.tsx
Normal file
62
frontend/src/components/Course/configureCourseCols.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
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_COURSE');
|
||||
|
||||
return [
|
||||
{
|
||||
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={`/course/course-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/course/course-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,157 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImageField from '../ImageField';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { Pagination } from '../Pagination';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
courses: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const CardCourses = ({
|
||||
courses,
|
||||
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_COURSES');
|
||||
|
||||
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 &&
|
||||
courses.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={`/courses/courses-view/?id=${item.id}`}
|
||||
className='text-lg font-bold leading-6 line-clamp-1'
|
||||
>
|
||||
{item.title}
|
||||
</Link>
|
||||
|
||||
<div className='ml-auto '>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/courses/courses-edit/?id=${item.id}`}
|
||||
pathView={`/courses/courses-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'>Title</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>{item.title}</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Description
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.description}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Instructors
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter
|
||||
.usersManyListFormatter(item.instructors)
|
||||
.join(', ')}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Students
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter
|
||||
.usersManyListFormatter(item.students)
|
||||
.join(', ')}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
DiscussionBoards
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter
|
||||
.discussion_boardsManyListFormatter(
|
||||
item.discussion_boards,
|
||||
)
|
||||
.join(', ')}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</li>
|
||||
))}
|
||||
{!loading && courses.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 CardCourses;
|
||||
@ -1,125 +0,0 @@
|
||||
import React from 'react';
|
||||
import CardBox from '../CardBox';
|
||||
import ImageField from '../ImageField';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import { Pagination } from '../Pagination';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
courses: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const ListCourses = ({
|
||||
courses,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
numPages,
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_COURSES');
|
||||
|
||||
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 &&
|
||||
courses.map((item) => (
|
||||
<div key={item.id}>
|
||||
<CardBox hasTable isList className={'rounded shadow-none'}>
|
||||
<div
|
||||
className={`flex ${bgColor} ${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||
>
|
||||
<Link
|
||||
href={`/courses/courses-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 '}>Title</p>
|
||||
<p className={'line-clamp-2'}>{item.title}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Description</p>
|
||||
<p className={'line-clamp-2'}>{item.description}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Instructors</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter
|
||||
.usersManyListFormatter(item.instructors)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Students</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter
|
||||
.usersManyListFormatter(item.students)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>
|
||||
DiscussionBoards
|
||||
</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter
|
||||
.discussion_boardsManyListFormatter(
|
||||
item.discussion_boards,
|
||||
)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/courses/courses-edit/?id=${item.id}`}
|
||||
pathView={`/courses/courses-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
))}
|
||||
{!loading && courses.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 ListCourses;
|
||||
@ -1,143 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_COURSES');
|
||||
|
||||
return [
|
||||
{
|
||||
field: 'title',
|
||||
headerName: 'Title',
|
||||
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: 'instructors',
|
||||
headerName: 'Instructors',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: false,
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
valueFormatter: ({ value }) =>
|
||||
dataFormatter.usersManyListFormatter(value).join(', '),
|
||||
renderEditCell: (params) => (
|
||||
<DataGridMultiSelect {...params} entityName={'users'} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
field: 'students',
|
||||
headerName: 'Students',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: false,
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
valueFormatter: ({ value }) =>
|
||||
dataFormatter.usersManyListFormatter(value).join(', '),
|
||||
renderEditCell: (params) => (
|
||||
<DataGridMultiSelect {...params} entityName={'users'} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
field: 'discussion_boards',
|
||||
headerName: 'DiscussionBoards',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: false,
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
valueFormatter: ({ value }) =>
|
||||
dataFormatter.discussion_boardsManyListFormatter(value).join(', '),
|
||||
renderEditCell: (params) => (
|
||||
<DataGridMultiSelect {...params} entityName={'discussion_boards'} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
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>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,130 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImageField from '../ImageField';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { Pagination } from '../Pagination';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
discussion_boards: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const CardDiscussion_boards = ({
|
||||
discussion_boards,
|
||||
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_DISCUSSION_BOARDS',
|
||||
);
|
||||
|
||||
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 &&
|
||||
discussion_boards.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={`/discussion_boards/discussion_boards-view/?id=${item.id}`}
|
||||
className='text-lg font-bold leading-6 line-clamp-1'
|
||||
>
|
||||
{item.topic}
|
||||
</Link>
|
||||
|
||||
<div className='ml-auto '>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/discussion_boards/discussion_boards-edit/?id=${item.id}`}
|
||||
pathView={`/discussion_boards/discussion_boards-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'>
|
||||
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'>Topic</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>{item.topic}</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>Posts</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter
|
||||
.postsManyListFormatter(item.posts)
|
||||
.join(', ')}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</li>
|
||||
))}
|
||||
{!loading && discussion_boards.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 CardDiscussion_boards;
|
||||
@ -1,108 +0,0 @@
|
||||
import React from 'react';
|
||||
import CardBox from '../CardBox';
|
||||
import ImageField from '../ImageField';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import { Pagination } from '../Pagination';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
discussion_boards: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const ListDiscussion_boards = ({
|
||||
discussion_boards,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
numPages,
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(
|
||||
currentUser,
|
||||
'UPDATE_DISCUSSION_BOARDS',
|
||||
);
|
||||
|
||||
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 &&
|
||||
discussion_boards.map((item) => (
|
||||
<div key={item.id}>
|
||||
<CardBox hasTable isList className={'rounded shadow-none'}>
|
||||
<div
|
||||
className={`flex ${bgColor} ${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||
>
|
||||
<Link
|
||||
href={`/discussion_boards/discussion_boards-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 '}>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 '}>Topic</p>
|
||||
<p className={'line-clamp-2'}>{item.topic}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Posts</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter
|
||||
.postsManyListFormatter(item.posts)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/discussion_boards/discussion_boards-edit/?id=${item.id}`}
|
||||
pathView={`/discussion_boards/discussion_boards-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
))}
|
||||
{!loading && discussion_boards.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 ListDiscussion_boards;
|
||||
@ -1,500 +0,0 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import BaseButton from '../BaseButton';
|
||||
import CardBoxModal from '../CardBoxModal';
|
||||
import CardBox from '../CardBox';
|
||||
import {
|
||||
fetch,
|
||||
update,
|
||||
deleteItem,
|
||||
setRefetch,
|
||||
deleteItemsByIds,
|
||||
} from '../../stores/discussion_boards/discussion_boardsSlice';
|
||||
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 './configureDiscussion_boardsCols';
|
||||
import _ from 'lodash';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { dataGridStyles } from '../../styles';
|
||||
|
||||
import ListDiscussion_boards from './ListDiscussion_boards';
|
||||
|
||||
const perPage = 10;
|
||||
|
||||
const TableSampleDiscussion_boards = ({
|
||||
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 {
|
||||
discussion_boards,
|
||||
loading,
|
||||
count,
|
||||
notify: discussion_boardsNotify,
|
||||
refetch,
|
||||
} = useAppSelector((state) => state.discussion_boards);
|
||||
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 (discussion_boardsNotify.showNotification) {
|
||||
notify(
|
||||
discussion_boardsNotify.typeNotification,
|
||||
discussion_boardsNotify.textNotification,
|
||||
);
|
||||
}
|
||||
}, [discussion_boardsNotify.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, `discussion_boards`, 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={discussion_boards ?? []}
|
||||
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'
|
||||
type='submit'
|
||||
color='info'
|
||||
label='Apply'
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className='my-2'
|
||||
type='reset'
|
||||
color='info'
|
||||
outline
|
||||
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>
|
||||
|
||||
{discussion_boards && Array.isArray(discussion_boards) && !showGrid && (
|
||||
<ListDiscussion_boards
|
||||
discussion_boards={discussion_boards}
|
||||
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 TableSampleDiscussion_boards;
|
||||
@ -1,113 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_DISCUSSION_BOARDS');
|
||||
|
||||
return [
|
||||
{
|
||||
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: 'topic',
|
||||
headerName: 'Topic',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'posts',
|
||||
headerName: 'Posts',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: false,
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
valueFormatter: ({ value }) =>
|
||||
dataFormatter.postsManyListFormatter(value).join(', '),
|
||||
renderEditCell: (params) => (
|
||||
<DataGridMultiSelect {...params} entityName={'posts'} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
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={`/discussion_boards/discussion_boards-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/discussion_boards/discussion_boards-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,114 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_ENROLLMENTS');
|
||||
|
||||
return [
|
||||
{
|
||||
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('students'),
|
||||
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: 'payment_status',
|
||||
headerName: 'PaymentStatus',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'actions',
|
||||
type: 'actions',
|
||||
minWidth: 30,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
getActions: (params: GridRowParams) => {
|
||||
return [
|
||||
<div key={params?.row?.id}>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={params?.row?.id}
|
||||
pathEdit={`/enrollments/enrollments-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/enrollments/enrollments-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,116 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_GRADES');
|
||||
|
||||
return [
|
||||
{
|
||||
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('students'),
|
||||
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: 'grade',
|
||||
headerName: 'Grade',
|
||||
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={`/grades/grades-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/grades/grades-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,500 +0,0 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import BaseButton from '../BaseButton';
|
||||
import CardBoxModal from '../CardBoxModal';
|
||||
import CardBox from '../CardBox';
|
||||
import {
|
||||
fetch,
|
||||
update,
|
||||
deleteItem,
|
||||
setRefetch,
|
||||
deleteItemsByIds,
|
||||
} from '../../stores/instructors/instructorsSlice';
|
||||
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 './configureInstructorsCols';
|
||||
import _ from 'lodash';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { dataGridStyles } from '../../styles';
|
||||
|
||||
import ListInstructors from './ListInstructors';
|
||||
|
||||
const perPage = 10;
|
||||
|
||||
const TableSampleInstructors = ({
|
||||
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 {
|
||||
instructors,
|
||||
loading,
|
||||
count,
|
||||
notify: instructorsNotify,
|
||||
refetch,
|
||||
} = useAppSelector((state) => state.instructors);
|
||||
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 (instructorsNotify.showNotification) {
|
||||
notify(
|
||||
instructorsNotify.typeNotification,
|
||||
instructorsNotify.textNotification,
|
||||
);
|
||||
}
|
||||
}, [instructorsNotify.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, `instructors`, 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={instructors ?? []}
|
||||
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'
|
||||
type='submit'
|
||||
color='info'
|
||||
label='Apply'
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className='my-2'
|
||||
type='reset'
|
||||
color='info'
|
||||
outline
|
||||
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>
|
||||
|
||||
{instructors && Array.isArray(instructors) && !showGrid && (
|
||||
<ListInstructors
|
||||
instructors={instructors}
|
||||
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 TableSampleInstructors;
|
||||
@ -1,113 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_INSTRUCTORS');
|
||||
|
||||
return [
|
||||
{
|
||||
field: 'user',
|
||||
headerName: 'User',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
getOptionValue: (value: any) => value?.id,
|
||||
getOptionLabel: (value: any) => value?.label,
|
||||
valueOptions: await callOptionsApi('users'),
|
||||
valueGetter: (params: GridValueGetterParams) =>
|
||||
params?.value?.id ?? params?.value,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'qualifications',
|
||||
headerName: 'Qualifications',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'courses',
|
||||
headerName: 'Courses',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: false,
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
valueFormatter: ({ value }) =>
|
||||
dataFormatter.coursesManyListFormatter(value).join(', '),
|
||||
renderEditCell: (params) => (
|
||||
<DataGridMultiSelect {...params} entityName={'courses'} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
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={`/instructors/instructors-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/instructors/instructors-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -11,7 +11,7 @@ import Link from 'next/link';
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
students: any[];
|
||||
mcs_pyq: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
@ -19,8 +19,8 @@ type Props = {
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const CardStudents = ({
|
||||
students,
|
||||
const CardMcs_pyq = ({
|
||||
mcs_pyq,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
@ -36,7 +36,7 @@ const CardStudents = ({
|
||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_STUDENTS');
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_MCS_PYQ');
|
||||
|
||||
return (
|
||||
<div className={'p-4'}>
|
||||
@ -46,7 +46,7 @@ const CardStudents = ({
|
||||
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
||||
>
|
||||
{!loading &&
|
||||
students.map((item, index) => (
|
||||
mcs_pyq.map((item, index) => (
|
||||
<li
|
||||
key={item.id}
|
||||
className={`overflow-hidden ${
|
||||
@ -59,61 +59,26 @@ const CardStudents = ({
|
||||
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={`/students/students-view/?id=${item.id}`}
|
||||
href={`/mcs_pyq/mcs_pyq-view/?id=${item.id}`}
|
||||
className='text-lg font-bold leading-6 line-clamp-1'
|
||||
>
|
||||
{item.user}
|
||||
{item.id}
|
||||
</Link>
|
||||
|
||||
<div className='ml-auto '>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/students/students-edit/?id=${item.id}`}
|
||||
pathView={`/students/students-view/?id=${item.id}`}
|
||||
pathEdit={`/mcs_pyq/mcs_pyq-edit/?id=${item.id}`}
|
||||
pathView={`/mcs_pyq/mcs_pyq-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'>User</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter.usersOneListFormatter(item.user)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Enrollments
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter
|
||||
.enrollmentsManyListFormatter(item.enrollments)
|
||||
.join(', ')}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Grades
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter
|
||||
.gradesManyListFormatter(item.grades)
|
||||
.join(', ')}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'></dl>
|
||||
</li>
|
||||
))}
|
||||
{!loading && students.length === 0 && (
|
||||
{!loading && mcs_pyq.length === 0 && (
|
||||
<div className='col-span-full flex items-center justify-center h-40'>
|
||||
<p className=''>No data to display</p>
|
||||
</div>
|
||||
@ -130,4 +95,4 @@ const CardStudents = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default CardStudents;
|
||||
export default CardMcs_pyq;
|
||||
@ -12,7 +12,7 @@ import Link from 'next/link';
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
students: any[];
|
||||
mcs_pyq: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
@ -20,8 +20,8 @@ type Props = {
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const ListStudents = ({
|
||||
students,
|
||||
const ListMcs_pyq = ({
|
||||
mcs_pyq,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
@ -29,7 +29,7 @@ const ListStudents = ({
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_STUDENTS');
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_MCS_PYQ');
|
||||
|
||||
const corners = useAppSelector((state) => state.style.corners);
|
||||
const bgColor = useAppSelector((state) => state.style.cardsColor);
|
||||
@ -39,7 +39,7 @@ const ListStudents = ({
|
||||
<div className='relative overflow-x-auto p-4 space-y-4'>
|
||||
{loading && <LoadingSpinner />}
|
||||
{!loading &&
|
||||
students.map((item) => (
|
||||
mcs_pyq.map((item) => (
|
||||
<div key={item.id}>
|
||||
<CardBox hasTable isList className={'rounded shadow-none'}>
|
||||
<div
|
||||
@ -48,48 +48,23 @@ const ListStudents = ({
|
||||
} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||
>
|
||||
<Link
|
||||
href={`/students/students-view/?id=${item.id}`}
|
||||
href={`/mcs_pyq/mcs_pyq-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 '}>User</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter.usersOneListFormatter(item.user)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Enrollments</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter
|
||||
.enrollmentsManyListFormatter(item.enrollments)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Grades</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter
|
||||
.gradesManyListFormatter(item.grades)
|
||||
.join(', ')}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
></Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/students/students-edit/?id=${item.id}`}
|
||||
pathView={`/students/students-view/?id=${item.id}`}
|
||||
pathEdit={`/mcs_pyq/mcs_pyq-edit/?id=${item.id}`}
|
||||
pathView={`/mcs_pyq/mcs_pyq-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
))}
|
||||
{!loading && students.length === 0 && (
|
||||
{!loading && mcs_pyq.length === 0 && (
|
||||
<div className='col-span-full flex items-center justify-center h-40'>
|
||||
<p className=''>No data to display</p>
|
||||
</div>
|
||||
@ -106,4 +81,4 @@ const ListStudents = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ListStudents;
|
||||
export default ListMcs_pyq;
|
||||
@ -10,21 +10,19 @@ import {
|
||||
deleteItem,
|
||||
setRefetch,
|
||||
deleteItemsByIds,
|
||||
} from '../../stores/courses/coursesSlice';
|
||||
} from '../../stores/mcs_pyq/mcs_pyqSlice';
|
||||
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 './configureCoursesCols';
|
||||
import { loadColumns } from './configureMcs_pyqCols';
|
||||
import _ from 'lodash';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { dataGridStyles } from '../../styles';
|
||||
|
||||
import ListCourses from './ListCourses';
|
||||
|
||||
const perPage = 10;
|
||||
|
||||
const TableSampleCourses = ({
|
||||
const TableSampleMcs_pyq = ({
|
||||
filterItems,
|
||||
setFilterItems,
|
||||
filters,
|
||||
@ -49,12 +47,12 @@ const TableSampleCourses = ({
|
||||
]);
|
||||
|
||||
const {
|
||||
courses,
|
||||
mcs_pyq,
|
||||
loading,
|
||||
count,
|
||||
notify: coursesNotify,
|
||||
notify: mcs_pyqNotify,
|
||||
refetch,
|
||||
} = useAppSelector((state) => state.courses);
|
||||
} = useAppSelector((state) => state.mcs_pyq);
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
const focusRing = useAppSelector((state) => state.style.focusRingColor);
|
||||
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
|
||||
@ -75,10 +73,10 @@ const TableSampleCourses = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (coursesNotify.showNotification) {
|
||||
notify(coursesNotify.typeNotification, coursesNotify.textNotification);
|
||||
if (mcs_pyqNotify.showNotification) {
|
||||
notify(mcs_pyqNotify.typeNotification, mcs_pyqNotify.textNotification);
|
||||
}
|
||||
}, [coursesNotify.showNotification]);
|
||||
}, [mcs_pyqNotify.showNotification]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
@ -183,7 +181,7 @@ const TableSampleCourses = ({
|
||||
useEffect(() => {
|
||||
if (!currentUser) return;
|
||||
|
||||
loadColumns(handleDeleteModalAction, `courses`, currentUser).then(
|
||||
loadColumns(handleDeleteModalAction, `mcs_pyq`, currentUser).then(
|
||||
(newCols) => setColumns(newCols),
|
||||
);
|
||||
}, [currentUser]);
|
||||
@ -217,7 +215,7 @@ const TableSampleCourses = ({
|
||||
sx={dataGridStyles}
|
||||
className={'datagrid--table'}
|
||||
getRowClassName={() => `datagrid--row`}
|
||||
rows={courses ?? []}
|
||||
rows={mcs_pyq ?? []}
|
||||
columns={columns}
|
||||
initialState={{
|
||||
pagination: {
|
||||
@ -466,18 +464,7 @@ const TableSampleCourses = ({
|
||||
<p>Are you sure you want to delete this item?</p>
|
||||
</CardBoxModal>
|
||||
|
||||
{courses && Array.isArray(courses) && !showGrid && (
|
||||
<ListCourses
|
||||
courses={courses}
|
||||
loading={loading}
|
||||
onDelete={handleDeleteModalAction}
|
||||
currentPage={currentPage}
|
||||
numPages={numPages}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
{showGrid && dataGrid}
|
||||
{dataGrid}
|
||||
|
||||
{selectedRows.length > 0 &&
|
||||
createPortal(
|
||||
@ -494,4 +481,4 @@ const TableSampleCourses = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default TableSampleCourses;
|
||||
export default TableSampleMcs_pyq;
|
||||
62
frontend/src/components/Mcs_pyq/configureMcs_pyqCols.tsx
Normal file
62
frontend/src/components/Mcs_pyq/configureMcs_pyqCols.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
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_MCS_PYQ');
|
||||
|
||||
return [
|
||||
{
|
||||
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={`/mcs_pyq/mcs_pyq-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/mcs_pyq/mcs_pyq-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,142 +0,0 @@
|
||||
import React from 'react';
|
||||
import ImageField from '../ImageField';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { Pagination } from '../Pagination';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
posts: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const CardPosts = ({
|
||||
posts,
|
||||
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_POSTS');
|
||||
|
||||
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 &&
|
||||
posts.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={`/posts/posts-view/?id=${item.id}`}
|
||||
className='text-lg font-bold leading-6 line-clamp-1'
|
||||
>
|
||||
{item.content}
|
||||
</Link>
|
||||
|
||||
<div className='ml-auto '>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/posts/posts-edit/?id=${item.id}`}
|
||||
pathView={`/posts/posts-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'>
|
||||
DiscussionBoard
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter.discussion_boardsOneListFormatter(
|
||||
item.discussion_board,
|
||||
)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>User</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter.usersOneListFormatter(item.user)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
Content
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{item.content}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
<div className='flex justify-between gap-x-4 py-3'>
|
||||
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||
PostedAt
|
||||
</dt>
|
||||
<dd className='flex items-start gap-x-2'>
|
||||
<div className='font-medium line-clamp-4'>
|
||||
{dataFormatter.dateTimeFormatter(item.posted_at)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</li>
|
||||
))}
|
||||
{!loading && posts.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 CardPosts;
|
||||
@ -1,114 +0,0 @@
|
||||
import React from 'react';
|
||||
import CardBox from '../CardBox';
|
||||
import ImageField from '../ImageField';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
import { useAppSelector } from '../../stores/hooks';
|
||||
import { Pagination } from '../Pagination';
|
||||
import LoadingSpinner from '../LoadingSpinner';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Props = {
|
||||
posts: any[];
|
||||
loading: boolean;
|
||||
onDelete: (id: string) => void;
|
||||
currentPage: number;
|
||||
numPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
};
|
||||
|
||||
const ListPosts = ({
|
||||
posts,
|
||||
loading,
|
||||
onDelete,
|
||||
currentPage,
|
||||
numPages,
|
||||
onPageChange,
|
||||
}: Props) => {
|
||||
const currentUser = useAppSelector((state) => state.auth.currentUser);
|
||||
const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_POSTS');
|
||||
|
||||
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 &&
|
||||
posts.map((item) => (
|
||||
<div key={item.id}>
|
||||
<CardBox hasTable isList className={'rounded shadow-none'}>
|
||||
<div
|
||||
className={`flex ${bgColor} ${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||
>
|
||||
<Link
|
||||
href={`/posts/posts-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 '}>
|
||||
DiscussionBoard
|
||||
</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter.discussion_boardsOneListFormatter(
|
||||
item.discussion_board,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>User</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter.usersOneListFormatter(item.user)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>Content</p>
|
||||
<p className={'line-clamp-2'}>{item.content}</p>
|
||||
</div>
|
||||
|
||||
<div className={'flex-1 px-3'}>
|
||||
<p className={'text-xs text-gray-500 '}>PostedAt</p>
|
||||
<p className={'line-clamp-2'}>
|
||||
{dataFormatter.dateTimeFormatter(item.posted_at)}
|
||||
</p>
|
||||
</div>
|
||||
</Link>
|
||||
<ListActionsPopover
|
||||
onDelete={onDelete}
|
||||
itemId={item.id}
|
||||
pathEdit={`/posts/posts-edit/?id=${item.id}`}
|
||||
pathView={`/posts/posts-view/?id=${item.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>
|
||||
</CardBox>
|
||||
</div>
|
||||
))}
|
||||
{!loading && posts.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 ListPosts;
|
||||
@ -1,497 +0,0 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import BaseButton from '../BaseButton';
|
||||
import CardBoxModal from '../CardBoxModal';
|
||||
import CardBox from '../CardBox';
|
||||
import {
|
||||
fetch,
|
||||
update,
|
||||
deleteItem,
|
||||
setRefetch,
|
||||
deleteItemsByIds,
|
||||
} from '../../stores/posts/postsSlice';
|
||||
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 './configurePostsCols';
|
||||
import _ from 'lodash';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { dataGridStyles } from '../../styles';
|
||||
|
||||
import ListPosts from './ListPosts';
|
||||
|
||||
const perPage = 10;
|
||||
|
||||
const TableSamplePosts = ({
|
||||
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 {
|
||||
posts,
|
||||
loading,
|
||||
count,
|
||||
notify: postsNotify,
|
||||
refetch,
|
||||
} = useAppSelector((state) => state.posts);
|
||||
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 (postsNotify.showNotification) {
|
||||
notify(postsNotify.typeNotification, postsNotify.textNotification);
|
||||
}
|
||||
}, [postsNotify.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, `posts`, 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={posts ?? []}
|
||||
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'
|
||||
type='submit'
|
||||
color='info'
|
||||
label='Apply'
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className='my-2'
|
||||
type='reset'
|
||||
color='info'
|
||||
outline
|
||||
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>
|
||||
|
||||
{posts && Array.isArray(posts) && !showGrid && (
|
||||
<ListPosts
|
||||
posts={posts}
|
||||
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 TableSamplePosts;
|
||||
@ -1,130 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_POSTS');
|
||||
|
||||
return [
|
||||
{
|
||||
field: 'discussion_board',
|
||||
headerName: 'DiscussionBoard',
|
||||
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('discussion_boards'),
|
||||
valueGetter: (params: GridValueGetterParams) =>
|
||||
params?.value?.id ?? params?.value,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'user',
|
||||
headerName: 'User',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
getOptionValue: (value: any) => value?.id,
|
||||
getOptionLabel: (value: any) => value?.label,
|
||||
valueOptions: await callOptionsApi('users'),
|
||||
valueGetter: (params: GridValueGetterParams) =>
|
||||
params?.value?.id ?? params?.value,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'content',
|
||||
headerName: 'Content',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'posted_at',
|
||||
headerName: 'PostedAt',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
type: 'dateTime',
|
||||
valueGetter: (params: GridValueGetterParams) =>
|
||||
new Date(params.row.posted_at),
|
||||
},
|
||||
|
||||
{
|
||||
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={`/posts/posts-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/posts/posts-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,497 +0,0 @@
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import BaseButton from '../BaseButton';
|
||||
import CardBoxModal from '../CardBoxModal';
|
||||
import CardBox from '../CardBox';
|
||||
import {
|
||||
fetch,
|
||||
update,
|
||||
deleteItem,
|
||||
setRefetch,
|
||||
deleteItemsByIds,
|
||||
} from '../../stores/students/studentsSlice';
|
||||
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 './configureStudentsCols';
|
||||
import _ from 'lodash';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import { dataGridStyles } from '../../styles';
|
||||
|
||||
import ListStudents from './ListStudents';
|
||||
|
||||
const perPage = 10;
|
||||
|
||||
const TableSampleStudents = ({
|
||||
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 {
|
||||
students,
|
||||
loading,
|
||||
count,
|
||||
notify: studentsNotify,
|
||||
refetch,
|
||||
} = useAppSelector((state) => state.students);
|
||||
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 (studentsNotify.showNotification) {
|
||||
notify(studentsNotify.typeNotification, studentsNotify.textNotification);
|
||||
}
|
||||
}, [studentsNotify.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, `students`, 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={students ?? []}
|
||||
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'
|
||||
type='submit'
|
||||
color='info'
|
||||
label='Apply'
|
||||
onClick={handleSubmit}
|
||||
/>
|
||||
<BaseButton
|
||||
className='my-2'
|
||||
type='reset'
|
||||
color='info'
|
||||
outline
|
||||
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>
|
||||
|
||||
{students && Array.isArray(students) && !showGrid && (
|
||||
<ListStudents
|
||||
students={students}
|
||||
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 TableSampleStudents;
|
||||
@ -1,120 +0,0 @@
|
||||
import React from 'react';
|
||||
import BaseIcon from '../BaseIcon';
|
||||
import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
GridActionsCellItem,
|
||||
GridRowParams,
|
||||
GridValueGetterParams,
|
||||
} from '@mui/x-data-grid';
|
||||
import ImageField from '../ImageField';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import DataGridMultiSelect from '../DataGridMultiSelect';
|
||||
import ListActionsPopover from '../ListActionsPopover';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
type Params = (id: string) => void;
|
||||
|
||||
export const loadColumns = async (
|
||||
onDelete: Params,
|
||||
entityName: string,
|
||||
|
||||
user,
|
||||
) => {
|
||||
async function callOptionsApi(entityName: string) {
|
||||
if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
|
||||
|
||||
try {
|
||||
const data = await axios(`/${entityName}/autocomplete?limit=100`);
|
||||
return data.data;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
const hasUpdatePermission = hasPermission(user, 'UPDATE_STUDENTS');
|
||||
|
||||
return [
|
||||
{
|
||||
field: 'user',
|
||||
headerName: 'User',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: hasUpdatePermission,
|
||||
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
getOptionValue: (value: any) => value?.id,
|
||||
getOptionLabel: (value: any) => value?.label,
|
||||
valueOptions: await callOptionsApi('users'),
|
||||
valueGetter: (params: GridValueGetterParams) =>
|
||||
params?.value?.id ?? params?.value,
|
||||
},
|
||||
|
||||
{
|
||||
field: 'enrollments',
|
||||
headerName: 'Enrollments',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: false,
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
valueFormatter: ({ value }) =>
|
||||
dataFormatter.enrollmentsManyListFormatter(value).join(', '),
|
||||
renderEditCell: (params) => (
|
||||
<DataGridMultiSelect {...params} entityName={'enrollments'} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
field: 'grades',
|
||||
headerName: 'Grades',
|
||||
flex: 1,
|
||||
minWidth: 120,
|
||||
filterable: false,
|
||||
headerClassName: 'datagrid--header',
|
||||
cellClassName: 'datagrid--cell',
|
||||
|
||||
editable: false,
|
||||
sortable: false,
|
||||
type: 'singleSelect',
|
||||
valueFormatter: ({ value }) =>
|
||||
dataFormatter.gradesManyListFormatter(value).join(', '),
|
||||
renderEditCell: (params) => (
|
||||
<DataGridMultiSelect {...params} entityName={'grades'} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
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={`/students/students-edit/?id=${params?.row?.id}`}
|
||||
pathView={`/students/students-view/?id=${params?.row?.id}`}
|
||||
hasUpdatePermission={hasUpdatePermission}
|
||||
/>
|
||||
</div>,
|
||||
];
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -17,9 +17,9 @@ export default function WebSiteFooter({ projectName }: WebSiteFooterProps) {
|
||||
const borders = useAppSelector((state) => state.style.borders);
|
||||
const websiteHeder = useAppSelector((state) => state.style.websiteHeder);
|
||||
|
||||
const style = FooterStyle.WITH_PAGES;
|
||||
const style = FooterStyle.WITH_PROJECT_NAME;
|
||||
|
||||
const design = FooterDesigns.DESIGN_DIVERSITY;
|
||||
const design = FooterDesigns.DEFAULT_DESIGN;
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -39,139 +39,6 @@ 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 };
|
||||
},
|
||||
|
||||
coursesManyListFormatter(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => item.title);
|
||||
},
|
||||
coursesOneListFormatter(val) {
|
||||
if (!val) return '';
|
||||
return val.title;
|
||||
},
|
||||
coursesManyListFormatterEdit(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => {
|
||||
return { id: item.id, label: item.title };
|
||||
});
|
||||
},
|
||||
coursesOneListFormatterEdit(val) {
|
||||
if (!val) return '';
|
||||
return { label: val.title, id: val.id };
|
||||
},
|
||||
|
||||
discussion_boardsManyListFormatter(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => item.topic);
|
||||
},
|
||||
discussion_boardsOneListFormatter(val) {
|
||||
if (!val) return '';
|
||||
return val.topic;
|
||||
},
|
||||
discussion_boardsManyListFormatterEdit(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => {
|
||||
return { id: item.id, label: item.topic };
|
||||
});
|
||||
},
|
||||
discussion_boardsOneListFormatterEdit(val) {
|
||||
if (!val) return '';
|
||||
return { label: val.topic, id: val.id };
|
||||
},
|
||||
|
||||
enrollmentsManyListFormatter(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => item.course);
|
||||
},
|
||||
enrollmentsOneListFormatter(val) {
|
||||
if (!val) return '';
|
||||
return val.course;
|
||||
},
|
||||
enrollmentsManyListFormatterEdit(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => {
|
||||
return { id: item.id, label: item.course };
|
||||
});
|
||||
},
|
||||
enrollmentsOneListFormatterEdit(val) {
|
||||
if (!val) return '';
|
||||
return { label: val.course, id: val.id };
|
||||
},
|
||||
|
||||
gradesManyListFormatter(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => item.grade);
|
||||
},
|
||||
gradesOneListFormatter(val) {
|
||||
if (!val) return '';
|
||||
return val.grade;
|
||||
},
|
||||
gradesManyListFormatterEdit(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => {
|
||||
return { id: item.id, label: item.grade };
|
||||
});
|
||||
},
|
||||
gradesOneListFormatterEdit(val) {
|
||||
if (!val) return '';
|
||||
return { label: val.grade, id: val.id };
|
||||
},
|
||||
|
||||
postsManyListFormatter(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => item.content);
|
||||
},
|
||||
postsOneListFormatter(val) {
|
||||
if (!val) return '';
|
||||
return val.content;
|
||||
},
|
||||
postsManyListFormatterEdit(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => {
|
||||
return { id: item.id, label: item.content };
|
||||
});
|
||||
},
|
||||
postsOneListFormatterEdit(val) {
|
||||
if (!val) return '';
|
||||
return { label: val.content, id: val.id };
|
||||
},
|
||||
|
||||
studentsManyListFormatter(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => item.user);
|
||||
},
|
||||
studentsOneListFormatter(val) {
|
||||
if (!val) return '';
|
||||
return val.user;
|
||||
},
|
||||
studentsManyListFormatterEdit(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => {
|
||||
return { id: item.id, label: item.user };
|
||||
});
|
||||
},
|
||||
studentsOneListFormatterEdit(val) {
|
||||
if (!val) return '';
|
||||
return { label: val.user, id: val.id };
|
||||
},
|
||||
|
||||
rolesManyListFormatter(val) {
|
||||
if (!val || !val.length) return [];
|
||||
return val.map((item) => item.name);
|
||||
|
||||
@ -16,94 +16,6 @@ const menuAside: MenuAsideItem[] = [
|
||||
icon: icon.mdiAccountGroup ?? icon.mdiTable,
|
||||
permissions: 'READ_USERS',
|
||||
},
|
||||
{
|
||||
href: '/analytics/analytics-list',
|
||||
label: 'Analytics',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiChartLine' in icon
|
||||
? icon['mdiChartLine' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_ANALYTICS',
|
||||
},
|
||||
{
|
||||
href: '/courses/courses-list',
|
||||
label: 'Courses',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiBookOpenPageVariant' in icon
|
||||
? icon['mdiBookOpenPageVariant' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_COURSES',
|
||||
},
|
||||
{
|
||||
href: '/discussion_boards/discussion_boards-list',
|
||||
label: 'Discussion boards',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiForumOutline' in icon
|
||||
? icon['mdiForumOutline' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_DISCUSSION_BOARDS',
|
||||
},
|
||||
{
|
||||
href: '/enrollments/enrollments-list',
|
||||
label: 'Enrollments',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiClipboardCheckOutline' in icon
|
||||
? icon['mdiClipboardCheckOutline' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_ENROLLMENTS',
|
||||
},
|
||||
{
|
||||
href: '/grades/grades-list',
|
||||
label: 'Grades',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiChartBar' in icon
|
||||
? icon['mdiChartBar' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_GRADES',
|
||||
},
|
||||
{
|
||||
href: '/instructors/instructors-list',
|
||||
label: 'Instructors',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiTeach' in icon
|
||||
? icon['mdiTeach' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_INSTRUCTORS',
|
||||
},
|
||||
{
|
||||
href: '/posts/posts-list',
|
||||
label: 'Posts',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiCommentTextOutline' in icon
|
||||
? icon['mdiCommentTextOutline' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_POSTS',
|
||||
},
|
||||
{
|
||||
href: '/students/students-list',
|
||||
label: 'Students',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon:
|
||||
'mdiSchool' in icon
|
||||
? icon['mdiSchool' as keyof typeof icon]
|
||||
: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_STUDENTS',
|
||||
},
|
||||
{
|
||||
href: '/roles/roles-list',
|
||||
label: 'Roles',
|
||||
@ -120,6 +32,22 @@ const menuAside: MenuAsideItem[] = [
|
||||
icon: icon.mdiShieldAccountOutline ?? icon.mdiTable,
|
||||
permissions: 'READ_PERMISSIONS',
|
||||
},
|
||||
{
|
||||
href: '/mcs_pyq/mcs_pyq-list',
|
||||
label: 'Mcs pyq',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_MCS_PYQ',
|
||||
},
|
||||
{
|
||||
href: '/course/course-list',
|
||||
label: 'Course',
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
icon: icon.mdiTable ?? icon.mdiTable,
|
||||
permissions: 'READ_COURSE',
|
||||
},
|
||||
{
|
||||
href: '/profile',
|
||||
label: 'Profile',
|
||||
|
||||
@ -1,163 +0,0 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/analytics/analyticsSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditAnalytics = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
course: null,
|
||||
|
||||
student_engagement: '',
|
||||
|
||||
completion_rate: '',
|
||||
|
||||
instructor_performance: '',
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { analytics } = useAppSelector((state) => state.analytics);
|
||||
|
||||
const { analyticsId } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: analyticsId }));
|
||||
}, [analyticsId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof analytics === 'object') {
|
||||
setInitialValues(analytics);
|
||||
}
|
||||
}, [analytics]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof analytics === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = analytics[el]),
|
||||
);
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [analytics]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: analyticsId, data }));
|
||||
await router.push('/analytics/analytics-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit analytics')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit analytics'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={initialValues.course}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='StudentEngagement'>
|
||||
<Field
|
||||
type='number'
|
||||
name='student_engagement'
|
||||
placeholder='StudentEngagement'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='CompletionRate'>
|
||||
<Field
|
||||
type='number'
|
||||
name='completion_rate'
|
||||
placeholder='CompletionRate'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='InstructorPerformance'>
|
||||
<Field
|
||||
type='number'
|
||||
name='instructor_performance'
|
||||
placeholder='InstructorPerformance'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/analytics/analytics-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditAnalytics.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_ANALYTICS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditAnalytics;
|
||||
@ -1,161 +0,0 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/analytics/analyticsSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditAnalyticsPage = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
course: null,
|
||||
|
||||
student_engagement: '',
|
||||
|
||||
completion_rate: '',
|
||||
|
||||
instructor_performance: '',
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { analytics } = useAppSelector((state) => state.analytics);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: id }));
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof analytics === 'object') {
|
||||
setInitialValues(analytics);
|
||||
}
|
||||
}, [analytics]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof analytics === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = analytics[el]),
|
||||
);
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [analytics]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: id, data }));
|
||||
await router.push('/analytics/analytics-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit analytics')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit analytics'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={initialValues.course}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='StudentEngagement'>
|
||||
<Field
|
||||
type='number'
|
||||
name='student_engagement'
|
||||
placeholder='StudentEngagement'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='CompletionRate'>
|
||||
<Field
|
||||
type='number'
|
||||
name='completion_rate'
|
||||
placeholder='CompletionRate'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='InstructorPerformance'>
|
||||
<Field
|
||||
type='number'
|
||||
name='instructor_performance'
|
||||
placeholder='InstructorPerformance'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/analytics/analytics-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditAnalyticsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_ANALYTICS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditAnalyticsPage;
|
||||
@ -1,172 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableAnalytics from '../../components/Analytics/TableAnalytics';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/analytics/analyticsSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const AnalyticsTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'StudentEngagement', title: 'student_engagement', number: 'true' },
|
||||
{ label: 'CompletionRate', title: 'completion_rate', number: 'true' },
|
||||
{
|
||||
label: 'InstructorPerformance',
|
||||
title: 'instructor_performance',
|
||||
number: 'true',
|
||||
},
|
||||
|
||||
{ label: 'Course', title: 'course' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_ANALYTICS');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getAnalyticsCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/analytics?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'analyticsCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Analytics')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Analytics'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/analytics/analytics-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getAnalyticsCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableAnalytics
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={false}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AnalyticsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_ANALYTICS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsTablesPage;
|
||||
@ -1,134 +0,0 @@
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiChartTimelineVariant,
|
||||
mdiMail,
|
||||
mdiUpload,
|
||||
} from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { create } from '../../stores/analytics/analyticsSlice';
|
||||
import { useAppDispatch } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import moment from 'moment';
|
||||
|
||||
const initialValues = {
|
||||
course: '',
|
||||
|
||||
student_engagement: '',
|
||||
|
||||
completion_rate: '',
|
||||
|
||||
instructor_performance: '',
|
||||
};
|
||||
|
||||
const AnalyticsNew = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(create(data));
|
||||
await router.push('/analytics/analytics-list');
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('New Item')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='New Item'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'courses'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='StudentEngagement'>
|
||||
<Field
|
||||
type='number'
|
||||
name='student_engagement'
|
||||
placeholder='StudentEngagement'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='CompletionRate'>
|
||||
<Field
|
||||
type='number'
|
||||
name='completion_rate'
|
||||
placeholder='CompletionRate'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='InstructorPerformance'>
|
||||
<Field
|
||||
type='number'
|
||||
name='instructor_performance'
|
||||
placeholder='InstructorPerformance'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/analytics/analytics-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AnalyticsNew.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'CREATE_ANALYTICS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsNew;
|
||||
@ -1,175 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableAnalytics from '../../components/Analytics/TableAnalytics';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/analytics/analyticsSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const AnalyticsTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'StudentEngagement', title: 'student_engagement', number: 'true' },
|
||||
{ label: 'CompletionRate', title: 'completion_rate', number: 'true' },
|
||||
{
|
||||
label: 'InstructorPerformance',
|
||||
title: 'instructor_performance',
|
||||
number: 'true',
|
||||
},
|
||||
|
||||
{ label: 'Course', title: 'course' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_ANALYTICS');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getAnalyticsCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/analytics?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'analyticsCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Analytics')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Analytics'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/analytics/analytics-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getAnalyticsCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
|
||||
<Link href={'/analytics/analytics-list'}>
|
||||
Back to <span className='capitalize'>table</span>
|
||||
</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableAnalytics
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={true}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
AnalyticsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_ANALYTICS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsTablesPage;
|
||||
@ -25,65 +25,57 @@ import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/instructors/instructorsSlice';
|
||||
import { update, fetch } from '../../stores/course/courseSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditInstructors = () => {
|
||||
const EditCourse = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
user: null,
|
||||
|
||||
qualifications: '',
|
||||
|
||||
courses: [],
|
||||
};
|
||||
const initVals = {};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { instructors } = useAppSelector((state) => state.instructors);
|
||||
const { course } = useAppSelector((state) => state.course);
|
||||
|
||||
const { instructorsId } = router.query;
|
||||
const { courseId } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: instructorsId }));
|
||||
}, [instructorsId]);
|
||||
dispatch(fetch({ id: courseId }));
|
||||
}, [courseId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof instructors === 'object') {
|
||||
setInitialValues(instructors);
|
||||
if (typeof course === 'object') {
|
||||
setInitialValues(course);
|
||||
}
|
||||
}, [instructors]);
|
||||
}, [course]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof instructors === 'object') {
|
||||
if (typeof course === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = instructors[el]),
|
||||
);
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = course[el]));
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [instructors]);
|
||||
}, [course]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: instructorsId, data }));
|
||||
await router.push('/instructors/instructors-list');
|
||||
await dispatch(update({ id: courseId, data }));
|
||||
await router.push('/course/course-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit instructors')}</title>
|
||||
<title>{getPageTitle('Edit course')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit instructors'}
|
||||
title={'Edit course'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -95,36 +87,6 @@ const EditInstructors = () => {
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='User' labelFor='user'>
|
||||
<Field
|
||||
name='user'
|
||||
id='user'
|
||||
component={SelectField}
|
||||
options={initialValues.user}
|
||||
itemRef={'users'}
|
||||
showField={'firstName'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Qualifications' hasTextareaHeight>
|
||||
<Field
|
||||
name='qualifications'
|
||||
as='textarea'
|
||||
placeholder='Qualifications'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Courses' labelFor='courses'>
|
||||
<Field
|
||||
name='courses'
|
||||
id='courses'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.courses}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
@ -134,7 +96,7 @@ const EditInstructors = () => {
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/instructors/instructors-list')}
|
||||
onClick={() => router.push('/course/course-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
@ -145,12 +107,12 @@ const EditInstructors = () => {
|
||||
);
|
||||
};
|
||||
|
||||
EditInstructors.getLayout = function getLayout(page: ReactElement) {
|
||||
EditCourse.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_INSTRUCTORS'}>
|
||||
<LayoutAuthenticated permission={'UPDATE_COURSE'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditInstructors;
|
||||
export default EditCourse;
|
||||
@ -25,26 +25,20 @@ import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/instructors/instructorsSlice';
|
||||
import { update, fetch } from '../../stores/course/courseSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditInstructorsPage = () => {
|
||||
const EditCoursePage = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
user: null,
|
||||
|
||||
qualifications: '',
|
||||
|
||||
courses: [],
|
||||
};
|
||||
const initVals = {};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { instructors } = useAppSelector((state) => state.instructors);
|
||||
const { course } = useAppSelector((state) => state.course);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
@ -53,35 +47,33 @@ const EditInstructorsPage = () => {
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof instructors === 'object') {
|
||||
setInitialValues(instructors);
|
||||
if (typeof course === 'object') {
|
||||
setInitialValues(course);
|
||||
}
|
||||
}, [instructors]);
|
||||
}, [course]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof instructors === 'object') {
|
||||
if (typeof course === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = instructors[el]),
|
||||
);
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = course[el]));
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [instructors]);
|
||||
}, [course]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: id, data }));
|
||||
await router.push('/instructors/instructors-list');
|
||||
await router.push('/course/course-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit instructors')}</title>
|
||||
<title>{getPageTitle('Edit course')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit instructors'}
|
||||
title={'Edit course'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -93,36 +85,6 @@ const EditInstructorsPage = () => {
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='User' labelFor='user'>
|
||||
<Field
|
||||
name='user'
|
||||
id='user'
|
||||
component={SelectField}
|
||||
options={initialValues.user}
|
||||
itemRef={'users'}
|
||||
showField={'firstName'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Qualifications' hasTextareaHeight>
|
||||
<Field
|
||||
name='qualifications'
|
||||
as='textarea'
|
||||
placeholder='Qualifications'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Courses' labelFor='courses'>
|
||||
<Field
|
||||
name='courses'
|
||||
id='courses'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.courses}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
@ -132,7 +94,7 @@ const EditInstructorsPage = () => {
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/instructors/instructors-list')}
|
||||
onClick={() => router.push('/course/course-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
@ -143,12 +105,12 @@ const EditInstructorsPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
EditInstructorsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
EditCoursePage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_INSTRUCTORS'}>
|
||||
<LayoutAuthenticated permission={'UPDATE_COURSE'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditInstructorsPage;
|
||||
export default EditCoursePage;
|
||||
@ -7,18 +7,18 @@ import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TablePosts from '../../components/Posts/TablePosts';
|
||||
import TableCourse from '../../components/Course/TableCourse';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/posts/postsSlice';
|
||||
import { setRefetch, uploadCsv } from '../../stores/course/courseSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const PostsTablesPage = () => {
|
||||
const CourseTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -28,18 +28,10 @@ const PostsTablesPage = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Content', title: 'content' },
|
||||
|
||||
{ label: 'PostedAt', title: 'posted_at', date: 'true' },
|
||||
|
||||
{ label: 'DiscussionBoard', title: 'discussion_board' },
|
||||
|
||||
{ label: 'User', title: 'user' },
|
||||
]);
|
||||
const [filters] = useState([]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_POSTS');
|
||||
currentUser && hasPermission(currentUser, 'CREATE_COURSE');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
@ -55,9 +47,9 @@ const PostsTablesPage = () => {
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getPostsCSV = async () => {
|
||||
const getCourseCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/posts?filetype=csv',
|
||||
url: '/course?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
@ -65,7 +57,7 @@ const PostsTablesPage = () => {
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'postsCSV.csv';
|
||||
link.download = 'courseCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
@ -85,12 +77,12 @@ const PostsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Posts')}</title>
|
||||
<title>{getPageTitle('Course')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Posts'
|
||||
title='Course'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -99,7 +91,7 @@ const PostsTablesPage = () => {
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/posts/posts-new'}
|
||||
href={'/course/course-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
@ -115,7 +107,7 @@ const PostsTablesPage = () => {
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getPostsCSV}
|
||||
onClick={getCourseCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
@ -129,14 +121,10 @@ const PostsTablesPage = () => {
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<Link href={'/posts/posts-table'}>Switch to Table</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TablePosts
|
||||
<TableCourse
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
@ -163,10 +151,10 @@ const PostsTablesPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
PostsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
CourseTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_POSTS'}>{page}</LayoutAuthenticated>
|
||||
<LayoutAuthenticated permission={'READ_COURSE'}>{page}</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostsTablesPage;
|
||||
export default CourseTablesPage;
|
||||
@ -27,26 +27,20 @@ import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { create } from '../../stores/instructors/instructorsSlice';
|
||||
import { create } from '../../stores/course/courseSlice';
|
||||
import { useAppDispatch } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import moment from 'moment';
|
||||
|
||||
const initialValues = {
|
||||
user: '',
|
||||
const initialValues = {};
|
||||
|
||||
qualifications: '',
|
||||
|
||||
courses: [],
|
||||
};
|
||||
|
||||
const InstructorsNew = () => {
|
||||
const CourseNew = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(create(data));
|
||||
await router.push('/instructors/instructors-list');
|
||||
await router.push('/course/course-list');
|
||||
};
|
||||
return (
|
||||
<>
|
||||
@ -67,34 +61,6 @@ const InstructorsNew = () => {
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='User' labelFor='user'>
|
||||
<Field
|
||||
name='user'
|
||||
id='user'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'users'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Qualifications' hasTextareaHeight>
|
||||
<Field
|
||||
name='qualifications'
|
||||
as='textarea'
|
||||
placeholder='Qualifications'
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Courses' labelFor='courses'>
|
||||
<Field
|
||||
name='courses'
|
||||
id='courses'
|
||||
itemRef={'courses'}
|
||||
options={[]}
|
||||
component={SelectFieldMany}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
@ -104,7 +70,7 @@ const InstructorsNew = () => {
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/instructors/instructors-list')}
|
||||
onClick={() => router.push('/course/course-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
@ -115,12 +81,12 @@ const InstructorsNew = () => {
|
||||
);
|
||||
};
|
||||
|
||||
InstructorsNew.getLayout = function getLayout(page: ReactElement) {
|
||||
CourseNew.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'CREATE_INSTRUCTORS'}>
|
||||
<LayoutAuthenticated permission={'CREATE_COURSE'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstructorsNew;
|
||||
export default CourseNew;
|
||||
@ -7,18 +7,18 @@ import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TablePosts from '../../components/Posts/TablePosts';
|
||||
import TableCourse from '../../components/Course/TableCourse';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/posts/postsSlice';
|
||||
import { setRefetch, uploadCsv } from '../../stores/course/courseSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const PostsTablesPage = () => {
|
||||
const CourseTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -28,18 +28,10 @@ const PostsTablesPage = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Content', title: 'content' },
|
||||
|
||||
{ label: 'PostedAt', title: 'posted_at', date: 'true' },
|
||||
|
||||
{ label: 'DiscussionBoard', title: 'discussion_board' },
|
||||
|
||||
{ label: 'User', title: 'user' },
|
||||
]);
|
||||
const [filters] = useState([]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_POSTS');
|
||||
currentUser && hasPermission(currentUser, 'CREATE_COURSE');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
@ -55,9 +47,9 @@ const PostsTablesPage = () => {
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getPostsCSV = async () => {
|
||||
const getCourseCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/posts?filetype=csv',
|
||||
url: '/course?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
@ -65,7 +57,7 @@ const PostsTablesPage = () => {
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'postsCSV.csv';
|
||||
link.download = 'courseCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
@ -85,12 +77,12 @@ const PostsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Posts')}</title>
|
||||
<title>{getPageTitle('Course')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Posts'
|
||||
title='Course'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -99,7 +91,7 @@ const PostsTablesPage = () => {
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/posts/posts-new'}
|
||||
href={'/course/course-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
@ -115,7 +107,7 @@ const PostsTablesPage = () => {
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getPostsCSV}
|
||||
onClick={getCourseCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
@ -128,14 +120,10 @@ const PostsTablesPage = () => {
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
|
||||
<Link href={'/posts/posts-list'}>
|
||||
Back to <span className='capitalize'>list</span>
|
||||
</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TablePosts
|
||||
<TableCourse
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
@ -162,10 +150,10 @@ const PostsTablesPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
PostsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
CourseTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_POSTS'}>{page}</LayoutAuthenticated>
|
||||
<LayoutAuthenticated permission={'READ_COURSE'}>{page}</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostsTablesPage;
|
||||
export default CourseTablesPage;
|
||||
@ -5,7 +5,7 @@ import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { fetch } from '../../stores/posts/postsSlice';
|
||||
import { fetch } from '../../stores/course/courseSlice';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
@ -20,10 +20,10 @@ import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
const PostsView = () => {
|
||||
const CourseView = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const { posts } = useAppSelector((state) => state.posts);
|
||||
const { course } = useAppSelector((state) => state.course);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
@ -39,67 +39,27 @@ const PostsView = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('View posts')}</title>
|
||||
<title>{getPageTitle('View course')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={removeLastCharacter('View posts')}
|
||||
title={removeLastCharacter('View course')}
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Edit'
|
||||
href={`/posts/posts-edit/?id=${id}`}
|
||||
href={`/course/course-edit/?id=${id}`}
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>DiscussionBoard</p>
|
||||
|
||||
<p>{posts?.discussion_board?.topic ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>User</p>
|
||||
|
||||
<p>{posts?.user?.firstName ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Content</p>
|
||||
{posts.content ? (
|
||||
<p dangerouslySetInnerHTML={{ __html: posts.content }} />
|
||||
) : (
|
||||
<p>No data</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FormField label='PostedAt'>
|
||||
{posts.posted_at ? (
|
||||
<DatePicker
|
||||
dateFormat='yyyy-MM-dd hh:mm'
|
||||
showTimeSelect
|
||||
selected={
|
||||
posts.posted_at
|
||||
? new Date(
|
||||
dayjs(posts.posted_at).format('YYYY-MM-DD hh:mm'),
|
||||
)
|
||||
: null
|
||||
}
|
||||
disabled
|
||||
/>
|
||||
) : (
|
||||
<p>No PostedAt</p>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Back'
|
||||
onClick={() => router.push('/posts/posts-list')}
|
||||
onClick={() => router.push('/course/course-list')}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
@ -107,10 +67,10 @@ const PostsView = () => {
|
||||
);
|
||||
};
|
||||
|
||||
PostsView.getLayout = function getLayout(page: ReactElement) {
|
||||
CourseView.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_POSTS'}>{page}</LayoutAuthenticated>
|
||||
<LayoutAuthenticated permission={'READ_COURSE'}>{page}</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostsView;
|
||||
export default CourseView;
|
||||
@ -1,173 +0,0 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/courses/coursesSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditCourses = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
title: '',
|
||||
|
||||
description: '',
|
||||
|
||||
instructors: [],
|
||||
|
||||
students: [],
|
||||
|
||||
discussion_boards: [],
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { courses } = useAppSelector((state) => state.courses);
|
||||
|
||||
const { coursesId } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: coursesId }));
|
||||
}, [coursesId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof courses === 'object') {
|
||||
setInitialValues(courses);
|
||||
}
|
||||
}, [courses]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof courses === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = courses[el]));
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [courses]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: coursesId, data }));
|
||||
await router.push('/courses/courses-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit courses')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit courses'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Title'>
|
||||
<Field name='title' placeholder='Title' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Description' hasTextareaHeight>
|
||||
<Field
|
||||
name='description'
|
||||
id='description'
|
||||
component={RichTextField}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Instructors' labelFor='instructors'>
|
||||
<Field
|
||||
name='instructors'
|
||||
id='instructors'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.instructors}
|
||||
itemRef={'users'}
|
||||
showField={'firstName'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Students' labelFor='students'>
|
||||
<Field
|
||||
name='students'
|
||||
id='students'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.students}
|
||||
itemRef={'users'}
|
||||
showField={'firstName'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='DiscussionBoards' labelFor='discussion_boards'>
|
||||
<Field
|
||||
name='discussion_boards'
|
||||
id='discussion_boards'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.discussion_boards}
|
||||
itemRef={'discussion_boards'}
|
||||
showField={'topic'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/courses/courses-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditCourses.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_COURSES'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditCourses;
|
||||
@ -1,171 +0,0 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/courses/coursesSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditCoursesPage = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
title: '',
|
||||
|
||||
description: '',
|
||||
|
||||
instructors: [],
|
||||
|
||||
students: [],
|
||||
|
||||
discussion_boards: [],
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { courses } = useAppSelector((state) => state.courses);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: id }));
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof courses === 'object') {
|
||||
setInitialValues(courses);
|
||||
}
|
||||
}, [courses]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof courses === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = courses[el]));
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [courses]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: id, data }));
|
||||
await router.push('/courses/courses-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit courses')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit courses'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Title'>
|
||||
<Field name='title' placeholder='Title' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Description' hasTextareaHeight>
|
||||
<Field
|
||||
name='description'
|
||||
id='description'
|
||||
component={RichTextField}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Instructors' labelFor='instructors'>
|
||||
<Field
|
||||
name='instructors'
|
||||
id='instructors'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.instructors}
|
||||
itemRef={'users'}
|
||||
showField={'firstName'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Students' labelFor='students'>
|
||||
<Field
|
||||
name='students'
|
||||
id='students'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.students}
|
||||
itemRef={'users'}
|
||||
showField={'firstName'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='DiscussionBoards' labelFor='discussion_boards'>
|
||||
<Field
|
||||
name='discussion_boards'
|
||||
id='discussion_boards'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.discussion_boards}
|
||||
itemRef={'discussion_boards'}
|
||||
showField={'topic'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/courses/courses-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditCoursesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_COURSES'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditCoursesPage;
|
||||
@ -1,173 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableCourses from '../../components/Courses/TableCourses';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/courses/coursesSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const CoursesTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Title', title: 'title' },
|
||||
{ label: 'Description', title: 'description' },
|
||||
|
||||
{ label: 'Instructors', title: 'instructors' },
|
||||
{ label: 'Students', title: 'students' },
|
||||
{ label: 'DiscussionBoards', title: 'discussion_boards' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_COURSES');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getCoursesCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/courses?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'coursesCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Courses')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Courses'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/courses/courses-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getCoursesCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<Link href={'/courses/courses-table'}>Switch to Table</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableCourses
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={false}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CoursesTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_COURSES'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default CoursesTablesPage;
|
||||
@ -1,144 +0,0 @@
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiChartTimelineVariant,
|
||||
mdiMail,
|
||||
mdiUpload,
|
||||
} from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { create } from '../../stores/courses/coursesSlice';
|
||||
import { useAppDispatch } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import moment from 'moment';
|
||||
|
||||
const initialValues = {
|
||||
title: '',
|
||||
|
||||
description: '',
|
||||
|
||||
instructors: [],
|
||||
|
||||
students: [],
|
||||
|
||||
discussion_boards: [],
|
||||
};
|
||||
|
||||
const CoursesNew = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(create(data));
|
||||
await router.push('/courses/courses-list');
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('New Item')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='New Item'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Title'>
|
||||
<Field name='title' placeholder='Title' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Description' hasTextareaHeight>
|
||||
<Field
|
||||
name='description'
|
||||
id='description'
|
||||
component={RichTextField}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Instructors' labelFor='instructors'>
|
||||
<Field
|
||||
name='instructors'
|
||||
id='instructors'
|
||||
itemRef={'users'}
|
||||
options={[]}
|
||||
component={SelectFieldMany}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Students' labelFor='students'>
|
||||
<Field
|
||||
name='students'
|
||||
id='students'
|
||||
itemRef={'users'}
|
||||
options={[]}
|
||||
component={SelectFieldMany}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='DiscussionBoards' labelFor='discussion_boards'>
|
||||
<Field
|
||||
name='discussion_boards'
|
||||
id='discussion_boards'
|
||||
itemRef={'discussion_boards'}
|
||||
options={[]}
|
||||
component={SelectFieldMany}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/courses/courses-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CoursesNew.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'CREATE_COURSES'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default CoursesNew;
|
||||
@ -1,172 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableCourses from '../../components/Courses/TableCourses';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/courses/coursesSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const CoursesTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Title', title: 'title' },
|
||||
{ label: 'Description', title: 'description' },
|
||||
|
||||
{ label: 'Instructors', title: 'instructors' },
|
||||
{ label: 'Students', title: 'students' },
|
||||
{ label: 'DiscussionBoards', title: 'discussion_boards' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_COURSES');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getCoursesCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/courses?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'coursesCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Courses')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Courses'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/courses/courses-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getCoursesCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
|
||||
<Link href={'/courses/courses-list'}>
|
||||
Back to <span className='capitalize'>list</span>
|
||||
</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableCourses
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={true}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CoursesTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_COURSES'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default CoursesTablesPage;
|
||||
@ -1,403 +0,0 @@
|
||||
import React, { ReactElement, useEffect } from 'react';
|
||||
import Head from 'next/head';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { fetch } from '../../stores/courses/coursesSlice';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import { getPageTitle } from '../../config';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
const CoursesView = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const { courses } = useAppSelector((state) => state.courses);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
function removeLastCharacter(str) {
|
||||
console.log(str, `str`);
|
||||
return str.slice(0, -1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('View courses')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={removeLastCharacter('View courses')}
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Edit'
|
||||
href={`/courses/courses-edit/?id=${id}`}
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Title</p>
|
||||
<p>{courses?.title}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Description</p>
|
||||
{courses.description ? (
|
||||
<p dangerouslySetInnerHTML={{ __html: courses.description }} />
|
||||
) : (
|
||||
<p>No data</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Instructors</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>First Name</th>
|
||||
|
||||
<th>Last Name</th>
|
||||
|
||||
<th>Phone Number</th>
|
||||
|
||||
<th>E-Mail</th>
|
||||
|
||||
<th>Disabled</th>
|
||||
|
||||
<th>Qualifications</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{courses.instructors &&
|
||||
Array.isArray(courses.instructors) &&
|
||||
courses.instructors.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/users/users-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='firstName'>{item.firstName}</td>
|
||||
|
||||
<td data-label='lastName'>{item.lastName}</td>
|
||||
|
||||
<td data-label='phoneNumber'>{item.phoneNumber}</td>
|
||||
|
||||
<td data-label='email'>{item.email}</td>
|
||||
|
||||
<td data-label='disabled'>
|
||||
{dataFormatter.booleanFormatter(item.disabled)}
|
||||
</td>
|
||||
|
||||
<td data-label='qualifications'>
|
||||
{item.qualifications}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!courses?.instructors?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Students</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>First Name</th>
|
||||
|
||||
<th>Last Name</th>
|
||||
|
||||
<th>Phone Number</th>
|
||||
|
||||
<th>E-Mail</th>
|
||||
|
||||
<th>Disabled</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{courses.students &&
|
||||
Array.isArray(courses.students) &&
|
||||
courses.students.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/users/users-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='firstName'>{item.firstName}</td>
|
||||
|
||||
<td data-label='lastName'>{item.lastName}</td>
|
||||
|
||||
<td data-label='phoneNumber'>{item.phoneNumber}</td>
|
||||
|
||||
<td data-label='email'>{item.email}</td>
|
||||
|
||||
<td data-label='disabled'>
|
||||
{dataFormatter.booleanFormatter(item.disabled)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!courses?.students?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>DiscussionBoards</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Topic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{courses.discussion_boards &&
|
||||
Array.isArray(courses.discussion_boards) &&
|
||||
courses.discussion_boards.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/discussion_boards/discussion_boards-view/?id=${item.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<td data-label='topic'>{item.topic}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!courses?.discussion_boards?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Analytics Course</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>StudentEngagement</th>
|
||||
|
||||
<th>CompletionRate</th>
|
||||
|
||||
<th>InstructorPerformance</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{courses.analytics_course &&
|
||||
Array.isArray(courses.analytics_course) &&
|
||||
courses.analytics_course.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/analytics/analytics-view/?id=${item.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<td data-label='student_engagement'>
|
||||
{item.student_engagement}
|
||||
</td>
|
||||
|
||||
<td data-label='completion_rate'>
|
||||
{item.completion_rate}
|
||||
</td>
|
||||
|
||||
<td data-label='instructor_performance'>
|
||||
{item.instructor_performance}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!courses?.analytics_course?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Discussion_boards Course</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Topic</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{courses.discussion_boards_course &&
|
||||
Array.isArray(courses.discussion_boards_course) &&
|
||||
courses.discussion_boards_course.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/discussion_boards/discussion_boards-view/?id=${item.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<td data-label='topic'>{item.topic}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!courses?.discussion_boards_course?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Enrollments Course</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PaymentStatus</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{courses.enrollments_course &&
|
||||
Array.isArray(courses.enrollments_course) &&
|
||||
courses.enrollments_course.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(
|
||||
`/enrollments/enrollments-view/?id=${item.id}`,
|
||||
)
|
||||
}
|
||||
>
|
||||
<td data-label='payment_status'>
|
||||
{item.payment_status}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!courses?.enrollments_course?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Grades Course</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Grade</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{courses.grades_course &&
|
||||
Array.isArray(courses.grades_course) &&
|
||||
courses.grades_course.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/grades/grades-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='grade'>{item.grade}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!courses?.grades_course?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Back'
|
||||
onClick={() => router.push('/courses/courses-list')}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
CoursesView.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_COURSES'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default CoursesView;
|
||||
@ -29,17 +29,10 @@ const Dashboard = () => {
|
||||
});
|
||||
|
||||
const [users, setUsers] = React.useState(loadingMessage);
|
||||
const [analytics, setAnalytics] = React.useState(loadingMessage);
|
||||
const [courses, setCourses] = React.useState(loadingMessage);
|
||||
const [discussion_boards, setDiscussion_boards] =
|
||||
React.useState(loadingMessage);
|
||||
const [enrollments, setEnrollments] = React.useState(loadingMessage);
|
||||
const [grades, setGrades] = React.useState(loadingMessage);
|
||||
const [instructors, setInstructors] = React.useState(loadingMessage);
|
||||
const [posts, setPosts] = React.useState(loadingMessage);
|
||||
const [students, setStudents] = React.useState(loadingMessage);
|
||||
const [roles, setRoles] = React.useState(loadingMessage);
|
||||
const [permissions, setPermissions] = React.useState(loadingMessage);
|
||||
const [mcs_pyq, setMcs_pyq] = React.useState(loadingMessage);
|
||||
const [course, setCourse] = React.useState(loadingMessage);
|
||||
|
||||
const [widgetsRole, setWidgetsRole] = React.useState({
|
||||
role: { value: '', label: '' },
|
||||
@ -50,32 +43,8 @@ const Dashboard = () => {
|
||||
const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
|
||||
|
||||
async function loadData() {
|
||||
const entities = [
|
||||
'users',
|
||||
'analytics',
|
||||
'courses',
|
||||
'discussion_boards',
|
||||
'enrollments',
|
||||
'grades',
|
||||
'instructors',
|
||||
'posts',
|
||||
'students',
|
||||
'roles',
|
||||
'permissions',
|
||||
];
|
||||
const fns = [
|
||||
setUsers,
|
||||
setAnalytics,
|
||||
setCourses,
|
||||
setDiscussion_boards,
|
||||
setEnrollments,
|
||||
setGrades,
|
||||
setInstructors,
|
||||
setPosts,
|
||||
setStudents,
|
||||
setRoles,
|
||||
setPermissions,
|
||||
];
|
||||
const entities = ['users', 'roles', 'permissions', 'mcs_pyq', 'course'];
|
||||
const fns = [setUsers, setRoles, setPermissions, setMcs_pyq, setCourse];
|
||||
|
||||
const requests = entities.map((entity, index) => {
|
||||
if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
|
||||
@ -221,296 +190,6 @@ const Dashboard = () => {
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_ANALYTICS') && (
|
||||
<Link href={'/analytics/analytics-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Analytics
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{analytics}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiChartLine' in icon
|
||||
? icon['mdiChartLine' as keyof typeof icon]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_COURSES') && (
|
||||
<Link href={'/courses/courses-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Courses
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{courses}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiBookOpenPageVariant' in icon
|
||||
? icon['mdiBookOpenPageVariant' as keyof typeof icon]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_DISCUSSION_BOARDS') && (
|
||||
<Link href={'/discussion_boards/discussion_boards-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Discussion boards
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{discussion_boards}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiForumOutline' in icon
|
||||
? icon['mdiForumOutline' as keyof typeof icon]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_ENROLLMENTS') && (
|
||||
<Link href={'/enrollments/enrollments-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Enrollments
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{enrollments}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiClipboardCheckOutline' in icon
|
||||
? icon[
|
||||
'mdiClipboardCheckOutline' as keyof typeof icon
|
||||
]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_GRADES') && (
|
||||
<Link href={'/grades/grades-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Grades
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{grades}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiChartBar' in icon
|
||||
? icon['mdiChartBar' as keyof typeof icon]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_INSTRUCTORS') && (
|
||||
<Link href={'/instructors/instructors-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Instructors
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{instructors}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiTeach' in icon
|
||||
? icon['mdiTeach' as keyof typeof icon]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_POSTS') && (
|
||||
<Link href={'/posts/posts-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Posts
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{posts}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiCommentTextOutline' in icon
|
||||
? icon['mdiCommentTextOutline' as keyof typeof icon]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_STUDENTS') && (
|
||||
<Link href={'/students/students-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Students
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{students}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={
|
||||
'mdiSchool' in icon
|
||||
? icon['mdiSchool' as keyof typeof icon]
|
||||
: icon.mdiTable || icon.mdiTable
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_ROLES') && (
|
||||
<Link href={'/roles/roles-list'}>
|
||||
<div
|
||||
@ -576,6 +255,70 @@ const Dashboard = () => {
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_MCS_PYQ') && (
|
||||
<Link href={'/mcs_pyq/mcs_pyq-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Mcs pyq
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{mcs_pyq}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{hasPermission(currentUser, 'READ_COURSE') && (
|
||||
<Link href={'/course/course-list'}>
|
||||
<div
|
||||
className={`${
|
||||
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||
} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
|
||||
>
|
||||
<div className='flex justify-between align-center'>
|
||||
<div>
|
||||
<div className='text-lg leading-tight text-gray-500 dark:text-gray-400'>
|
||||
Course
|
||||
</div>
|
||||
<div className='text-3xl leading-tight font-semibold'>
|
||||
{course}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<BaseIcon
|
||||
className={`${iconsColor}`}
|
||||
w='w-16'
|
||||
h='h-16'
|
||||
size={48}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
path={icon.mdiTable || icon.mdiTable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</SectionMain>
|
||||
</>
|
||||
|
||||
@ -1,177 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableDiscussion_boards from '../../components/Discussion_boards/TableDiscussion_boards';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import {
|
||||
setRefetch,
|
||||
uploadCsv,
|
||||
} from '../../stores/discussion_boards/discussion_boardsSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const Discussion_boardsTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Topic', title: 'topic' },
|
||||
|
||||
{ label: 'Course', title: 'course' },
|
||||
|
||||
{ label: 'Posts', title: 'posts' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_DISCUSSION_BOARDS');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getDiscussion_boardsCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/discussion_boards?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'discussion_boardsCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Discussion_boards')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Discussion_boards'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/discussion_boards/discussion_boards-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getDiscussion_boardsCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<Link href={'/discussion_boards/discussion_boards-table'}>
|
||||
Switch to Table
|
||||
</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableDiscussion_boards
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={false}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Discussion_boardsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_DISCUSSION_BOARDS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default Discussion_boardsTablesPage;
|
||||
@ -1,174 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableDiscussion_boards from '../../components/Discussion_boards/TableDiscussion_boards';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import {
|
||||
setRefetch,
|
||||
uploadCsv,
|
||||
} from '../../stores/discussion_boards/discussion_boardsSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const Discussion_boardsTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Topic', title: 'topic' },
|
||||
|
||||
{ label: 'Course', title: 'course' },
|
||||
|
||||
{ label: 'Posts', title: 'posts' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_DISCUSSION_BOARDS');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getDiscussion_boardsCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/discussion_boards?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'discussion_boardsCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Discussion_boards')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Discussion_boards'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/discussion_boards/discussion_boards-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getDiscussion_boardsCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
|
||||
<Link href={'/discussion_boards/discussion_boards-list'}>
|
||||
Back to <span className='capitalize'>list</span>
|
||||
</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableDiscussion_boards
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={true}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Discussion_boardsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_DISCUSSION_BOARDS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default Discussion_boardsTablesPage;
|
||||
@ -1,169 +0,0 @@
|
||||
import React, { ReactElement, useEffect } from 'react';
|
||||
import Head from 'next/head';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { fetch } from '../../stores/discussion_boards/discussion_boardsSlice';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import { getPageTitle } from '../../config';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
const Discussion_boardsView = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const { discussion_boards } = useAppSelector(
|
||||
(state) => state.discussion_boards,
|
||||
);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
function removeLastCharacter(str) {
|
||||
console.log(str, `str`);
|
||||
return str.slice(0, -1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('View discussion_boards')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={removeLastCharacter('View discussion_boards')}
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Edit'
|
||||
href={`/discussion_boards/discussion_boards-edit/?id=${id}`}
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Course</p>
|
||||
|
||||
<p>{discussion_boards?.course?.title ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Topic</p>
|
||||
<p>{discussion_boards?.topic}</p>
|
||||
</div>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Posts</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PostedAt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{discussion_boards.posts &&
|
||||
Array.isArray(discussion_boards.posts) &&
|
||||
discussion_boards.posts.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/posts/posts-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='posted_at'>
|
||||
{dataFormatter.dateTimeFormatter(item.posted_at)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!discussion_boards?.posts?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Posts DiscussionBoard</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PostedAt</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{discussion_boards.posts_discussion_board &&
|
||||
Array.isArray(discussion_boards.posts_discussion_board) &&
|
||||
discussion_boards.posts_discussion_board.map(
|
||||
(item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/posts/posts-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='posted_at'>
|
||||
{dataFormatter.dateTimeFormatter(item.posted_at)}
|
||||
</td>
|
||||
</tr>
|
||||
),
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!discussion_boards?.posts_discussion_board?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Back'
|
||||
onClick={() =>
|
||||
router.push('/discussion_boards/discussion_boards-list')
|
||||
}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Discussion_boardsView.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_DISCUSSION_BOARDS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default Discussion_boardsView;
|
||||
@ -1,160 +0,0 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/enrollments/enrollmentsSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditEnrollments = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
student: null,
|
||||
|
||||
course: null,
|
||||
|
||||
payment_status: '',
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { enrollments } = useAppSelector((state) => state.enrollments);
|
||||
|
||||
const { enrollmentsId } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: enrollmentsId }));
|
||||
}, [enrollmentsId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof enrollments === 'object') {
|
||||
setInitialValues(enrollments);
|
||||
}
|
||||
}, [enrollments]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof enrollments === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = enrollments[el]),
|
||||
);
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [enrollments]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: enrollmentsId, data }));
|
||||
await router.push('/enrollments/enrollments-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit enrollments')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit enrollments'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Student' labelFor='student'>
|
||||
<Field
|
||||
name='student'
|
||||
id='student'
|
||||
component={SelectField}
|
||||
options={initialValues.student}
|
||||
itemRef={'students'}
|
||||
showField={'user'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={initialValues.course}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='PaymentStatus' labelFor='payment_status'>
|
||||
<Field
|
||||
name='payment_status'
|
||||
id='payment_status'
|
||||
component='select'
|
||||
>
|
||||
<option value='pending'>pending</option>
|
||||
|
||||
<option value='completed'>completed</option>
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/enrollments/enrollments-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditEnrollments.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_ENROLLMENTS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditEnrollments;
|
||||
@ -1,158 +0,0 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/enrollments/enrollmentsSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditEnrollmentsPage = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
student: null,
|
||||
|
||||
course: null,
|
||||
|
||||
payment_status: '',
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { enrollments } = useAppSelector((state) => state.enrollments);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: id }));
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof enrollments === 'object') {
|
||||
setInitialValues(enrollments);
|
||||
}
|
||||
}, [enrollments]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof enrollments === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = enrollments[el]),
|
||||
);
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [enrollments]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: id, data }));
|
||||
await router.push('/enrollments/enrollments-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit enrollments')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit enrollments'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Student' labelFor='student'>
|
||||
<Field
|
||||
name='student'
|
||||
id='student'
|
||||
component={SelectField}
|
||||
options={initialValues.student}
|
||||
itemRef={'students'}
|
||||
showField={'user'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={initialValues.course}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='PaymentStatus' labelFor='payment_status'>
|
||||
<Field
|
||||
name='payment_status'
|
||||
id='payment_status'
|
||||
component='select'
|
||||
>
|
||||
<option value='pending'>pending</option>
|
||||
|
||||
<option value='completed'>completed</option>
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/enrollments/enrollments-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditEnrollmentsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_ENROLLMENTS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditEnrollmentsPage;
|
||||
@ -1,130 +0,0 @@
|
||||
import {
|
||||
mdiAccount,
|
||||
mdiChartTimelineVariant,
|
||||
mdiMail,
|
||||
mdiUpload,
|
||||
} from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { create } from '../../stores/enrollments/enrollmentsSlice';
|
||||
import { useAppDispatch } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import moment from 'moment';
|
||||
|
||||
const initialValues = {
|
||||
student: '',
|
||||
|
||||
course: '',
|
||||
|
||||
payment_status: 'pending',
|
||||
};
|
||||
|
||||
const EnrollmentsNew = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(create(data));
|
||||
await router.push('/enrollments/enrollments-list');
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('New Item')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='New Item'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Student' labelFor='student'>
|
||||
<Field
|
||||
name='student'
|
||||
id='student'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'students'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'courses'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='PaymentStatus' labelFor='payment_status'>
|
||||
<Field
|
||||
name='payment_status'
|
||||
id='payment_status'
|
||||
component='select'
|
||||
>
|
||||
<option value='pending'>pending</option>
|
||||
|
||||
<option value='completed'>completed</option>
|
||||
</Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/enrollments/enrollments-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EnrollmentsNew.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'CREATE_ENROLLMENTS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrollmentsNew;
|
||||
@ -1,179 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableEnrollments from '../../components/Enrollments/TableEnrollments';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import {
|
||||
setRefetch,
|
||||
uploadCsv,
|
||||
} from '../../stores/enrollments/enrollmentsSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const EnrollmentsTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Student', title: 'student' },
|
||||
|
||||
{ label: 'Course', title: 'course' },
|
||||
|
||||
{
|
||||
label: 'PaymentStatus',
|
||||
title: 'payment_status',
|
||||
type: 'enum',
|
||||
options: ['pending', 'completed'],
|
||||
},
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_ENROLLMENTS');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getEnrollmentsCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/enrollments?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'enrollmentsCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Enrollments')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Enrollments'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/enrollments/enrollments-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getEnrollmentsCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
|
||||
<Link href={'/enrollments/enrollments-list'}>
|
||||
Back to <span className='capitalize'>table</span>
|
||||
</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableEnrollments
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={true}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EnrollmentsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_ENROLLMENTS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EnrollmentsTablesPage;
|
||||
@ -1,175 +0,0 @@
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { ReactElement, useState } from 'react';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableInstructors from '../../components/Instructors/TableInstructors';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import {
|
||||
setRefetch,
|
||||
uploadCsv,
|
||||
} from '../../stores/instructors/instructorsSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const InstructorsTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
const [showTableView, setShowTableView] = useState(false);
|
||||
|
||||
const { currentUser } = useAppSelector((state) => state.auth);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Qualifications', title: 'qualifications' },
|
||||
|
||||
{ label: 'User', title: 'user' },
|
||||
|
||||
{ label: 'Courses', title: 'courses' },
|
||||
]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_INSTRUCTORS');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
id: uniqueId(),
|
||||
fields: {
|
||||
filterValue: '',
|
||||
filterValueFrom: '',
|
||||
filterValueTo: '',
|
||||
selectedField: '',
|
||||
},
|
||||
};
|
||||
newItem.fields.selectedField = filters[0].title;
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getInstructorsCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/instructors?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
const type = response.headers['content-type'];
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'instructorsCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
const onModalConfirm = async () => {
|
||||
if (!csvFile) return;
|
||||
await dispatch(uploadCsv(csvFile));
|
||||
dispatch(setRefetch(true));
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
const onModalCancel = () => {
|
||||
setCsvFile(null);
|
||||
setIsModalActive(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Instructors')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Instructors'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/instructors/instructors-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
)}
|
||||
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Filter'
|
||||
onClick={addFilter}
|
||||
/>
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getInstructorsCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Upload CSV'
|
||||
onClick={() => setIsModalActive(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<Link href={'/instructors/instructors-table'}>Switch to Table</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableInstructors
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
showGrid={false}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
<CardBoxModal
|
||||
title='Upload CSV'
|
||||
buttonColor='info'
|
||||
buttonLabel={'Confirm'}
|
||||
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||
isActive={isModalActive}
|
||||
onConfirm={onModalConfirm}
|
||||
onCancel={onModalCancel}
|
||||
>
|
||||
<DragDropFilePicker
|
||||
file={csvFile}
|
||||
setFile={setCsvFile}
|
||||
formats={'.csv'}
|
||||
/>
|
||||
</CardBoxModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
InstructorsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_INSTRUCTORS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstructorsTablesPage;
|
||||
@ -1,127 +0,0 @@
|
||||
import React, { ReactElement, useEffect } from 'react';
|
||||
import Head from 'next/head';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { fetch } from '../../stores/instructors/instructorsSlice';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import { getPageTitle } from '../../config';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import CardBox from '../../components/CardBox';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
const InstructorsView = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const { instructors } = useAppSelector((state) => state.instructors);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
function removeLastCharacter(str) {
|
||||
console.log(str, `str`);
|
||||
return str.slice(0, -1);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id }));
|
||||
}, [dispatch, id]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('View instructors')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={removeLastCharacter('View instructors')}
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Edit'
|
||||
href={`/instructors/instructors-edit/?id=${id}`}
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>User</p>
|
||||
|
||||
<p>{instructors?.user?.firstName ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<FormField label='Multi Text' hasTextareaHeight>
|
||||
<textarea
|
||||
className={'w-full'}
|
||||
disabled
|
||||
value={instructors?.qualifications}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<>
|
||||
<p className={'block font-bold mb-2'}>Courses</p>
|
||||
<CardBox
|
||||
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||
hasTable
|
||||
>
|
||||
<div className='overflow-x-auto'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{instructors.courses &&
|
||||
Array.isArray(instructors.courses) &&
|
||||
instructors.courses.map((item: any) => (
|
||||
<tr
|
||||
key={item.id}
|
||||
onClick={() =>
|
||||
router.push(`/courses/courses-view/?id=${item.id}`)
|
||||
}
|
||||
>
|
||||
<td data-label='title'>{item.title}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{!instructors?.courses?.length && (
|
||||
<div className={'text-center py-4'}>No data</div>
|
||||
)}
|
||||
</CardBox>
|
||||
</>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Back'
|
||||
onClick={() => router.push('/instructors/instructors-list')}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
InstructorsView.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_INSTRUCTORS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstructorsView;
|
||||
@ -25,70 +25,57 @@ import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import {
|
||||
update,
|
||||
fetch,
|
||||
} from '../../stores/discussion_boards/discussion_boardsSlice';
|
||||
import { update, fetch } from '../../stores/mcs_pyq/mcs_pyqSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditDiscussion_boards = () => {
|
||||
const EditMcs_pyq = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
course: null,
|
||||
|
||||
topic: '',
|
||||
|
||||
posts: [],
|
||||
};
|
||||
const initVals = {};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { discussion_boards } = useAppSelector(
|
||||
(state) => state.discussion_boards,
|
||||
);
|
||||
const { mcs_pyq } = useAppSelector((state) => state.mcs_pyq);
|
||||
|
||||
const { discussion_boardsId } = router.query;
|
||||
const { mcs_pyqId } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: discussion_boardsId }));
|
||||
}, [discussion_boardsId]);
|
||||
dispatch(fetch({ id: mcs_pyqId }));
|
||||
}, [mcs_pyqId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof discussion_boards === 'object') {
|
||||
setInitialValues(discussion_boards);
|
||||
if (typeof mcs_pyq === 'object') {
|
||||
setInitialValues(mcs_pyq);
|
||||
}
|
||||
}, [discussion_boards]);
|
||||
}, [mcs_pyq]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof discussion_boards === 'object') {
|
||||
if (typeof mcs_pyq === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = discussion_boards[el]),
|
||||
);
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = mcs_pyq[el]));
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [discussion_boards]);
|
||||
}, [mcs_pyq]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: discussion_boardsId, data }));
|
||||
await router.push('/discussion_boards/discussion_boards-list');
|
||||
await dispatch(update({ id: mcs_pyqId, data }));
|
||||
await router.push('/mcs_pyq/mcs_pyq-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit discussion_boards')}</title>
|
||||
<title>{getPageTitle('Edit mcs_pyq')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit discussion_boards'}
|
||||
title={'Edit mcs_pyq'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -100,32 +87,6 @@ const EditDiscussion_boards = () => {
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={initialValues.course}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Topic'>
|
||||
<Field name='topic' placeholder='Topic' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Posts' labelFor='posts'>
|
||||
<Field
|
||||
name='posts'
|
||||
id='posts'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.posts}
|
||||
itemRef={'posts'}
|
||||
showField={'content'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
@ -135,9 +96,7 @@ const EditDiscussion_boards = () => {
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() =>
|
||||
router.push('/discussion_boards/discussion_boards-list')
|
||||
}
|
||||
onClick={() => router.push('/mcs_pyq/mcs_pyq-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
@ -148,12 +107,12 @@ const EditDiscussion_boards = () => {
|
||||
);
|
||||
};
|
||||
|
||||
EditDiscussion_boards.getLayout = function getLayout(page: ReactElement) {
|
||||
EditMcs_pyq.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_DISCUSSION_BOARDS'}>
|
||||
<LayoutAuthenticated permission={'UPDATE_MCS_PYQ'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditDiscussion_boards;
|
||||
export default EditMcs_pyq;
|
||||
@ -25,31 +25,20 @@ import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import {
|
||||
update,
|
||||
fetch,
|
||||
} from '../../stores/discussion_boards/discussion_boardsSlice';
|
||||
import { update, fetch } from '../../stores/mcs_pyq/mcs_pyqSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditDiscussion_boardsPage = () => {
|
||||
const EditMcs_pyqPage = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
course: null,
|
||||
|
||||
topic: '',
|
||||
|
||||
posts: [],
|
||||
};
|
||||
const initVals = {};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { discussion_boards } = useAppSelector(
|
||||
(state) => state.discussion_boards,
|
||||
);
|
||||
const { mcs_pyq } = useAppSelector((state) => state.mcs_pyq);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
@ -58,35 +47,33 @@ const EditDiscussion_boardsPage = () => {
|
||||
}, [id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof discussion_boards === 'object') {
|
||||
setInitialValues(discussion_boards);
|
||||
if (typeof mcs_pyq === 'object') {
|
||||
setInitialValues(mcs_pyq);
|
||||
}
|
||||
}, [discussion_boards]);
|
||||
}, [mcs_pyq]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof discussion_boards === 'object') {
|
||||
if (typeof mcs_pyq === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
Object.keys(initVals).forEach(
|
||||
(el) => (newInitialVal[el] = discussion_boards[el]),
|
||||
);
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = mcs_pyq[el]));
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [discussion_boards]);
|
||||
}, [mcs_pyq]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: id, data }));
|
||||
await router.push('/discussion_boards/discussion_boards-list');
|
||||
await router.push('/mcs_pyq/mcs_pyq-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit discussion_boards')}</title>
|
||||
<title>{getPageTitle('Edit mcs_pyq')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit discussion_boards'}
|
||||
title={'Edit mcs_pyq'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -98,32 +85,6 @@ const EditDiscussion_boardsPage = () => {
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='Course' labelFor='course'>
|
||||
<Field
|
||||
name='course'
|
||||
id='course'
|
||||
component={SelectField}
|
||||
options={initialValues.course}
|
||||
itemRef={'courses'}
|
||||
showField={'title'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Topic'>
|
||||
<Field name='topic' placeholder='Topic' />
|
||||
</FormField>
|
||||
|
||||
<FormField label='Posts' labelFor='posts'>
|
||||
<Field
|
||||
name='posts'
|
||||
id='posts'
|
||||
component={SelectFieldMany}
|
||||
options={initialValues.posts}
|
||||
itemRef={'posts'}
|
||||
showField={'content'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
@ -133,9 +94,7 @@ const EditDiscussion_boardsPage = () => {
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() =>
|
||||
router.push('/discussion_boards/discussion_boards-list')
|
||||
}
|
||||
onClick={() => router.push('/mcs_pyq/mcs_pyq-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
@ -146,12 +105,12 @@ const EditDiscussion_boardsPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
EditDiscussion_boardsPage.getLayout = function getLayout(page: ReactElement) {
|
||||
EditMcs_pyqPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_DISCUSSION_BOARDS'}>
|
||||
<LayoutAuthenticated permission={'UPDATE_MCS_PYQ'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditDiscussion_boardsPage;
|
||||
export default EditMcs_pyqPage;
|
||||
@ -7,18 +7,18 @@ import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableStudents from '../../components/Students/TableStudents';
|
||||
import TableMcs_pyq from '../../components/Mcs_pyq/TableMcs_pyq';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import { setRefetch, uploadCsv } from '../../stores/students/studentsSlice';
|
||||
import { setRefetch, uploadCsv } from '../../stores/mcs_pyq/mcs_pyqSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const StudentsTablesPage = () => {
|
||||
const Mcs_pyqTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -28,15 +28,10 @@ const StudentsTablesPage = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'User', title: 'user' },
|
||||
|
||||
{ label: 'Enrollments', title: 'enrollments' },
|
||||
{ label: 'Grades', title: 'grades' },
|
||||
]);
|
||||
const [filters] = useState([]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_STUDENTS');
|
||||
currentUser && hasPermission(currentUser, 'CREATE_MCS_PYQ');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
@ -52,9 +47,9 @@ const StudentsTablesPage = () => {
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getStudentsCSV = async () => {
|
||||
const getMcs_pyqCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/students?filetype=csv',
|
||||
url: '/mcs_pyq?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
@ -62,7 +57,7 @@ const StudentsTablesPage = () => {
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'studentsCSV.csv';
|
||||
link.download = 'mcs_pyqCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
@ -82,12 +77,12 @@ const StudentsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Students')}</title>
|
||||
<title>{getPageTitle('Mcs_pyq')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Students'
|
||||
title='Mcs_pyq'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -96,7 +91,7 @@ const StudentsTablesPage = () => {
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/students/students-new'}
|
||||
href={'/mcs_pyq/mcs_pyq-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
@ -112,7 +107,7 @@ const StudentsTablesPage = () => {
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getStudentsCSV}
|
||||
onClick={getMcs_pyqCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
@ -126,14 +121,10 @@ const StudentsTablesPage = () => {
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
</div>
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<Link href={'/students/students-table'}>Switch to Table</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableStudents
|
||||
<TableMcs_pyq
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
@ -160,12 +151,12 @@ const StudentsTablesPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
StudentsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
Mcs_pyqTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_STUDENTS'}>
|
||||
<LayoutAuthenticated permission={'READ_MCS_PYQ'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudentsTablesPage;
|
||||
export default Mcs_pyqTablesPage;
|
||||
@ -27,26 +27,20 @@ import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { create } from '../../stores/students/studentsSlice';
|
||||
import { create } from '../../stores/mcs_pyq/mcs_pyqSlice';
|
||||
import { useAppDispatch } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import moment from 'moment';
|
||||
|
||||
const initialValues = {
|
||||
user: '',
|
||||
const initialValues = {};
|
||||
|
||||
enrollments: [],
|
||||
|
||||
grades: [],
|
||||
};
|
||||
|
||||
const StudentsNew = () => {
|
||||
const Mcs_pyqNew = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(create(data));
|
||||
await router.push('/students/students-list');
|
||||
await router.push('/mcs_pyq/mcs_pyq-list');
|
||||
};
|
||||
return (
|
||||
<>
|
||||
@ -67,36 +61,6 @@ const StudentsNew = () => {
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='User' labelFor='user'>
|
||||
<Field
|
||||
name='user'
|
||||
id='user'
|
||||
component={SelectField}
|
||||
options={[]}
|
||||
itemRef={'users'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Enrollments' labelFor='enrollments'>
|
||||
<Field
|
||||
name='enrollments'
|
||||
id='enrollments'
|
||||
itemRef={'enrollments'}
|
||||
options={[]}
|
||||
component={SelectFieldMany}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Grades' labelFor='grades'>
|
||||
<Field
|
||||
name='grades'
|
||||
id='grades'
|
||||
itemRef={'grades'}
|
||||
options={[]}
|
||||
component={SelectFieldMany}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
@ -106,7 +70,7 @@ const StudentsNew = () => {
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/students/students-list')}
|
||||
onClick={() => router.push('/mcs_pyq/mcs_pyq-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
@ -117,12 +81,12 @@ const StudentsNew = () => {
|
||||
);
|
||||
};
|
||||
|
||||
StudentsNew.getLayout = function getLayout(page: ReactElement) {
|
||||
Mcs_pyqNew.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'CREATE_STUDENTS'}>
|
||||
<LayoutAuthenticated permission={'CREATE_MCS_PYQ'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudentsNew;
|
||||
export default Mcs_pyqNew;
|
||||
@ -7,21 +7,18 @@ import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
import TableInstructors from '../../components/Instructors/TableInstructors';
|
||||
import TableMcs_pyq from '../../components/Mcs_pyq/TableMcs_pyq';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import axios from 'axios';
|
||||
import Link from 'next/link';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import CardBoxModal from '../../components/CardBoxModal';
|
||||
import DragDropFilePicker from '../../components/DragDropFilePicker';
|
||||
import {
|
||||
setRefetch,
|
||||
uploadCsv,
|
||||
} from '../../stores/instructors/instructorsSlice';
|
||||
import { setRefetch, uploadCsv } from '../../stores/mcs_pyq/mcs_pyqSlice';
|
||||
|
||||
import { hasPermission } from '../../helpers/userPermissions';
|
||||
|
||||
const InstructorsTablesPage = () => {
|
||||
const Mcs_pyqTablesPage = () => {
|
||||
const [filterItems, setFilterItems] = useState([]);
|
||||
const [csvFile, setCsvFile] = useState<File | null>(null);
|
||||
const [isModalActive, setIsModalActive] = useState(false);
|
||||
@ -31,16 +28,10 @@ const InstructorsTablesPage = () => {
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [filters] = useState([
|
||||
{ label: 'Qualifications', title: 'qualifications' },
|
||||
|
||||
{ label: 'User', title: 'user' },
|
||||
|
||||
{ label: 'Courses', title: 'courses' },
|
||||
]);
|
||||
const [filters] = useState([]);
|
||||
|
||||
const hasCreatePermission =
|
||||
currentUser && hasPermission(currentUser, 'CREATE_INSTRUCTORS');
|
||||
currentUser && hasPermission(currentUser, 'CREATE_MCS_PYQ');
|
||||
|
||||
const addFilter = () => {
|
||||
const newItem = {
|
||||
@ -56,9 +47,9 @@ const InstructorsTablesPage = () => {
|
||||
setFilterItems([...filterItems, newItem]);
|
||||
};
|
||||
|
||||
const getInstructorsCSV = async () => {
|
||||
const getMcs_pyqCSV = async () => {
|
||||
const response = await axios({
|
||||
url: '/instructors?filetype=csv',
|
||||
url: '/mcs_pyq?filetype=csv',
|
||||
method: 'GET',
|
||||
responseType: 'blob',
|
||||
});
|
||||
@ -66,7 +57,7 @@ const InstructorsTablesPage = () => {
|
||||
const blob = new Blob([response.data], { type: type });
|
||||
const link = document.createElement('a');
|
||||
link.href = window.URL.createObjectURL(blob);
|
||||
link.download = 'instructorsCSV.csv';
|
||||
link.download = 'mcs_pyqCSV.csv';
|
||||
link.click();
|
||||
};
|
||||
|
||||
@ -86,12 +77,12 @@ const InstructorsTablesPage = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Instructors')}</title>
|
||||
<title>{getPageTitle('Mcs_pyq')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title='Instructors'
|
||||
title='Mcs_pyq'
|
||||
main
|
||||
>
|
||||
{''}
|
||||
@ -100,7 +91,7 @@ const InstructorsTablesPage = () => {
|
||||
{hasCreatePermission && (
|
||||
<BaseButton
|
||||
className={'mr-3'}
|
||||
href={'/instructors/instructors-new'}
|
||||
href={'/mcs_pyq/mcs_pyq-new'}
|
||||
color='info'
|
||||
label='New Item'
|
||||
/>
|
||||
@ -116,7 +107,7 @@ const InstructorsTablesPage = () => {
|
||||
className={'mr-3'}
|
||||
color='info'
|
||||
label='Download CSV'
|
||||
onClick={getInstructorsCSV}
|
||||
onClick={getMcs_pyqCSV}
|
||||
/>
|
||||
|
||||
{hasCreatePermission && (
|
||||
@ -129,14 +120,10 @@ const InstructorsTablesPage = () => {
|
||||
|
||||
<div className='md:inline-flex items-center ms-auto'>
|
||||
<div id='delete-rows-button'></div>
|
||||
|
||||
<Link href={'/instructors/instructors-list'}>
|
||||
Back to <span className='capitalize'>list</span>
|
||||
</Link>
|
||||
</div>
|
||||
</CardBox>
|
||||
<CardBox className='mb-6' hasTable>
|
||||
<TableInstructors
|
||||
<TableMcs_pyq
|
||||
filterItems={filterItems}
|
||||
setFilterItems={setFilterItems}
|
||||
filters={filters}
|
||||
@ -163,12 +150,12 @@ const InstructorsTablesPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
InstructorsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
Mcs_pyqTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_INSTRUCTORS'}>
|
||||
<LayoutAuthenticated permission={'READ_MCS_PYQ'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default InstructorsTablesPage;
|
||||
export default Mcs_pyqTablesPage;
|
||||
@ -5,7 +5,7 @@ import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { fetch } from '../../stores/analytics/analyticsSlice';
|
||||
import { fetch } from '../../stores/mcs_pyq/mcs_pyqSlice';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
@ -20,10 +20,10 @@ import { mdiChartTimelineVariant } from '@mdi/js';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import FormField from '../../components/FormField';
|
||||
|
||||
const AnalyticsView = () => {
|
||||
const Mcs_pyqView = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const { analytics } = useAppSelector((state) => state.analytics);
|
||||
const { mcs_pyq } = useAppSelector((state) => state.mcs_pyq);
|
||||
|
||||
const { id } = router.query;
|
||||
|
||||
@ -39,48 +39,27 @@ const AnalyticsView = () => {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('View analytics')}</title>
|
||||
<title>{getPageTitle('View mcs_pyq')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={removeLastCharacter('View analytics')}
|
||||
title={removeLastCharacter('View mcs_pyq')}
|
||||
main
|
||||
>
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Edit'
|
||||
href={`/analytics/analytics-edit/?id=${id}`}
|
||||
href={`/mcs_pyq/mcs_pyq-edit/?id=${id}`}
|
||||
/>
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>Course</p>
|
||||
|
||||
<p>{analytics?.course?.title ?? 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>StudentEngagement</p>
|
||||
<p>{analytics?.student_engagement || 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>CompletionRate</p>
|
||||
<p>{analytics?.completion_rate || 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<div className={'mb-4'}>
|
||||
<p className={'block font-bold mb-2'}>InstructorPerformance</p>
|
||||
<p>{analytics?.instructor_performance || 'No data'}</p>
|
||||
</div>
|
||||
|
||||
<BaseDivider />
|
||||
|
||||
<BaseButton
|
||||
color='info'
|
||||
label='Back'
|
||||
onClick={() => router.push('/analytics/analytics-list')}
|
||||
onClick={() => router.push('/mcs_pyq/mcs_pyq-list')}
|
||||
/>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
@ -88,12 +67,12 @@ const AnalyticsView = () => {
|
||||
);
|
||||
};
|
||||
|
||||
AnalyticsView.getLayout = function getLayout(page: ReactElement) {
|
||||
Mcs_pyqView.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'READ_ANALYTICS'}>
|
||||
<LayoutAuthenticated permission={'READ_MCS_PYQ'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default AnalyticsView;
|
||||
export default Mcs_pyqView;
|
||||
@ -1,175 +0,0 @@
|
||||
import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
|
||||
import Head from 'next/head';
|
||||
import React, { ReactElement, useEffect, useState } from 'react';
|
||||
import DatePicker from 'react-datepicker';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import CardBox from '../../components/CardBox';
|
||||
import LayoutAuthenticated from '../../layouts/Authenticated';
|
||||
import SectionMain from '../../components/SectionMain';
|
||||
import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
|
||||
import { getPageTitle } from '../../config';
|
||||
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import FormField from '../../components/FormField';
|
||||
import BaseDivider from '../../components/BaseDivider';
|
||||
import BaseButtons from '../../components/BaseButtons';
|
||||
import BaseButton from '../../components/BaseButton';
|
||||
import FormCheckRadio from '../../components/FormCheckRadio';
|
||||
import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
|
||||
import FormFilePicker from '../../components/FormFilePicker';
|
||||
import FormImagePicker from '../../components/FormImagePicker';
|
||||
import { SelectField } from '../../components/SelectField';
|
||||
import { SelectFieldMany } from '../../components/SelectFieldMany';
|
||||
import { SwitchField } from '../../components/SwitchField';
|
||||
import { RichTextField } from '../../components/RichTextField';
|
||||
|
||||
import { update, fetch } from '../../stores/posts/postsSlice';
|
||||
import { useAppDispatch, useAppSelector } from '../../stores/hooks';
|
||||
import { useRouter } from 'next/router';
|
||||
import { saveFile } from '../../helpers/fileSaver';
|
||||
import dataFormatter from '../../helpers/dataFormatter';
|
||||
import ImageField from '../../components/ImageField';
|
||||
|
||||
const EditPosts = () => {
|
||||
const router = useRouter();
|
||||
const dispatch = useAppDispatch();
|
||||
const initVals = {
|
||||
discussion_board: null,
|
||||
|
||||
user: null,
|
||||
|
||||
content: '',
|
||||
|
||||
posted_at: new Date(),
|
||||
};
|
||||
const [initialValues, setInitialValues] = useState(initVals);
|
||||
|
||||
const { posts } = useAppSelector((state) => state.posts);
|
||||
|
||||
const { postsId } = router.query;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetch({ id: postsId }));
|
||||
}, [postsId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof posts === 'object') {
|
||||
setInitialValues(posts);
|
||||
}
|
||||
}, [posts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof posts === 'object') {
|
||||
const newInitialVal = { ...initVals };
|
||||
|
||||
Object.keys(initVals).forEach((el) => (newInitialVal[el] = posts[el]));
|
||||
|
||||
setInitialValues(newInitialVal);
|
||||
}
|
||||
}, [posts]);
|
||||
|
||||
const handleSubmit = async (data) => {
|
||||
await dispatch(update({ id: postsId, data }));
|
||||
await router.push('/posts/posts-list');
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>{getPageTitle('Edit posts')}</title>
|
||||
</Head>
|
||||
<SectionMain>
|
||||
<SectionTitleLineWithButton
|
||||
icon={mdiChartTimelineVariant}
|
||||
title={'Edit posts'}
|
||||
main
|
||||
>
|
||||
{''}
|
||||
</SectionTitleLineWithButton>
|
||||
<CardBox>
|
||||
<Formik
|
||||
enableReinitialize
|
||||
initialValues={initialValues}
|
||||
onSubmit={(values) => handleSubmit(values)}
|
||||
>
|
||||
<Form>
|
||||
<FormField label='DiscussionBoard' labelFor='discussion_board'>
|
||||
<Field
|
||||
name='discussion_board'
|
||||
id='discussion_board'
|
||||
component={SelectField}
|
||||
options={initialValues.discussion_board}
|
||||
itemRef={'discussion_boards'}
|
||||
showField={'topic'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='User' labelFor='user'>
|
||||
<Field
|
||||
name='user'
|
||||
id='user'
|
||||
component={SelectField}
|
||||
options={initialValues.user}
|
||||
itemRef={'users'}
|
||||
showField={'firstName'}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='Content' hasTextareaHeight>
|
||||
<Field
|
||||
name='content'
|
||||
id='content'
|
||||
component={RichTextField}
|
||||
></Field>
|
||||
</FormField>
|
||||
|
||||
<FormField label='PostedAt'>
|
||||
<DatePicker
|
||||
dateFormat='yyyy-MM-dd hh:mm'
|
||||
showTimeSelect
|
||||
selected={
|
||||
initialValues.posted_at
|
||||
? new Date(
|
||||
dayjs(initialValues.posted_at).format(
|
||||
'YYYY-MM-DD hh:mm',
|
||||
),
|
||||
)
|
||||
: null
|
||||
}
|
||||
onChange={(date) =>
|
||||
setInitialValues({ ...initialValues, posted_at: date })
|
||||
}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<BaseDivider />
|
||||
<BaseButtons>
|
||||
<BaseButton type='submit' color='info' label='Submit' />
|
||||
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||
<BaseButton
|
||||
type='reset'
|
||||
color='danger'
|
||||
outline
|
||||
label='Cancel'
|
||||
onClick={() => router.push('/posts/posts-list')}
|
||||
/>
|
||||
</BaseButtons>
|
||||
</Form>
|
||||
</Formik>
|
||||
</CardBox>
|
||||
</SectionMain>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
EditPosts.getLayout = function getLayout(page: ReactElement) {
|
||||
return (
|
||||
<LayoutAuthenticated permission={'UPDATE_POSTS'}>
|
||||
{page}
|
||||
</LayoutAuthenticated>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditPosts;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user