Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
5accde0a96 Updated via schema editor on 2025-08-19 18:57 2025-08-19 18:57:57 +00:00
111 changed files with 1277 additions and 17313 deletions

5
.gitignore vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
},
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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, {

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = [];

View File

@ -0,0 +1 @@
{}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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>,
];
},
},
];
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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>,
];
},
},
];
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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