diff --git a/.gitignore b/.gitignore index e427ff3..d0eb167 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/ */node_modules/ */build/ + +**/node_modules/ +**/build/ +.DS_Store +.env \ No newline at end of file diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json index 25eae1c..de12cf6 100644 --- a/app-shell/src/_schema.json +++ b/app-shell/src/_schema.json @@ -1,5 +1,4 @@ - - { - "Initial version": "{\"iv\":\"Um+F5WvIAWV87a6q\",\"encryptedData\":\"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\"}" -} + "Initial version": "{\"iv\":\"Um+F5WvIAWV87a6q\",\"encryptedData\":\"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\"}", + "01": "{\"iv\":\"+SKiXoA7NPwwbkIy\",\"encryptedData\":\"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\"}" +} \ No newline at end of file diff --git a/backend/src/db/api/employees.js b/backend/src/db/api/employees.js index ff2b184..ccecf31 100644 --- a/backend/src/db/api/employees.js +++ b/backend/src/db/api/employees.js @@ -18,6 +18,7 @@ module.exports = class EmployeesDBApi { name: data.name || null, email: data.email || null, phone: data.phone || null, + password_hash: data.password_hash || null, importHash: data.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -29,6 +30,10 @@ module.exports = class EmployeesDBApi { transaction, }); + await employees.setRole(data.role || null, { + transaction, + }); + return employees; } @@ -43,6 +48,7 @@ module.exports = class EmployeesDBApi { name: item.name || null, email: item.email || null, phone: item.phone || null, + password_hash: item.password_hash || null, importHash: item.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -73,6 +79,9 @@ module.exports = class EmployeesDBApi { if (data.phone !== undefined) updatePayload.phone = data.phone; + if (data.password_hash !== undefined) + updatePayload.password_hash = data.password_hash; + updatePayload.updatedById = currentUser.id; await employees.update(updatePayload, { transaction }); @@ -85,6 +94,14 @@ module.exports = class EmployeesDBApi { ); } + if (data.role !== undefined) { + await employees.setRole( + data.role, + + { transaction }, + ); + } + return employees; } @@ -150,10 +167,19 @@ module.exports = class EmployeesDBApi { transaction, }); + output.notification_logs_employee = + await employees.getNotification_logs_employee({ + transaction, + }); + output.department = await employees.getDepartment({ transaction, }); + output.role = await employees.getRole({ + transaction, + }); + return output; } @@ -195,6 +221,32 @@ module.exports = class EmployeesDBApi { } : {}, }, + + { + model: db.roles, + as: 'role', + + where: filter.role + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.role + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + name: { + [Op.or]: filter.role + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, ]; if (filter) { @@ -226,6 +278,17 @@ module.exports = class EmployeesDBApi { }; } + if (filter.password_hash) { + where = { + ...where, + [Op.and]: Utils.ilike( + 'employees', + 'password_hash', + filter.password_hash, + ), + }; + } + if (filter.active !== undefined) { where = { ...where, diff --git a/backend/src/db/api/notification_logs.js b/backend/src/db/api/notification_logs.js new file mode 100644 index 0000000..34760ab --- /dev/null +++ b/backend/src/db/api/notification_logs.js @@ -0,0 +1,289 @@ +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 Notification_logsDBApi { + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const notification_logs = await db.notification_logs.create( + { + id: data.id || undefined, + + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + await notification_logs.setEmployee(data.employee || null, { + transaction, + }); + + return notification_logs; + } + + 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 notification_logsData = 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 notification_logs = await db.notification_logs.bulkCreate( + notification_logsData, + { transaction }, + ); + + // For each item created, replace relation files + + return notification_logs; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const notification_logs = await db.notification_logs.findByPk( + id, + {}, + { transaction }, + ); + + const updatePayload = {}; + + updatePayload.updatedById = currentUser.id; + + await notification_logs.update(updatePayload, { transaction }); + + if (data.employee !== undefined) { + await notification_logs.setEmployee( + data.employee, + + { transaction }, + ); + } + + return notification_logs; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const notification_logs = await db.notification_logs.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of notification_logs) { + await record.update({ deletedBy: currentUser.id }, { transaction }); + } + for (const record of notification_logs) { + await record.destroy({ transaction }); + } + }); + + return notification_logs; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const notification_logs = await db.notification_logs.findByPk(id, options); + + await notification_logs.update( + { + deletedBy: currentUser.id, + }, + { + transaction, + }, + ); + + await notification_logs.destroy({ + transaction, + }); + + return notification_logs; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const notification_logs = await db.notification_logs.findOne( + { where }, + { transaction }, + ); + + if (!notification_logs) { + return notification_logs; + } + + const output = notification_logs.get({ plain: true }); + + output.employee = await notification_logs.getEmployee({ + 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.employees, + as: 'employee', + + where: filter.employee + ? { + [Op.or]: [ + { + id: { + [Op.in]: filter.employee + .split('|') + .map((term) => Utils.uuid(term)), + }, + }, + { + name: { + [Op.or]: filter.employee + .split('|') + .map((term) => ({ [Op.iLike]: `%${term}%` })), + }, + }, + ], + } + : {}, + }, + ]; + + 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.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.notification_logs.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('notification_logs', 'id', query), + ], + }; + } + + const records = await db.notification_logs.findAll({ + attributes: ['id', 'id'], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['id', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.id, + })); + } +}; diff --git a/backend/src/db/api/roles.js b/backend/src/db/api/roles.js index 7a6596d..2911e1d 100644 --- a/backend/src/db/api/roles.js +++ b/backend/src/db/api/roles.js @@ -141,6 +141,10 @@ module.exports = class RolesDBApi { transaction, }); + output.employees_role = await roles.getEmployees_role({ + transaction, + }); + output.permissions = await roles.getPermissions({ transaction, }); diff --git a/backend/src/db/migrations/1746445548702.js b/backend/src/db/migrations/1746445548702.js new file mode 100644 index 0000000..7dabf2f --- /dev/null +++ b/backend/src/db/migrations/1746445548702.js @@ -0,0 +1,49 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'employees', + 'password_hash', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('employees', 'password_hash', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1746445584795.js b/backend/src/db/migrations/1746445584795.js new file mode 100644 index 0000000..cd188a2 --- /dev/null +++ b/backend/src/db/migrations/1746445584795.js @@ -0,0 +1,52 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'employees', + 'roleId', + { + type: Sequelize.DataTypes.UUID, + + references: { + model: 'roles', + key: 'id', + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('employees', 'roleId', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1746445630402.js b/backend/src/db/migrations/1746445630402.js new file mode 100644 index 0000000..378bf1f --- /dev/null +++ b/backend/src/db/migrations/1746445630402.js @@ -0,0 +1,54 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'notification_logs', + 'employeeId', + { + type: Sequelize.DataTypes.UUID, + + references: { + model: 'employees', + key: 'id', + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('notification_logs', 'employeeId', { + transaction, + }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/models/employees.js b/backend/src/db/models/employees.js index ec3e152..1a0fe04 100644 --- a/backend/src/db/models/employees.js +++ b/backend/src/db/models/employees.js @@ -26,6 +26,10 @@ module.exports = function (sequelize, DataTypes) { type: DataTypes.TEXT, }, + password_hash: { + type: DataTypes.TEXT, + }, + importHash: { type: DataTypes.STRING(255), allowNull: true, @@ -50,6 +54,14 @@ module.exports = function (sequelize, DataTypes) { constraints: false, }); + db.employees.hasMany(db.notification_logs, { + as: 'notification_logs_employee', + foreignKey: { + name: 'employeeId', + }, + constraints: false, + }); + //end loop db.employees.belongsTo(db.departments, { @@ -60,6 +72,14 @@ module.exports = function (sequelize, DataTypes) { constraints: false, }); + db.employees.belongsTo(db.roles, { + as: 'role', + foreignKey: { + name: 'roleId', + }, + constraints: false, + }); + db.employees.belongsTo(db.users, { as: 'createdBy', }); diff --git a/backend/src/db/models/notification_logs.js b/backend/src/db/models/notification_logs.js new file mode 100644 index 0000000..c07cd6d --- /dev/null +++ b/backend/src/db/models/notification_logs.js @@ -0,0 +1,53 @@ +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 notification_logs = sequelize.define( + 'notification_logs', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + notification_logs.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.notification_logs.belongsTo(db.employees, { + as: 'employee', + foreignKey: { + name: 'employeeId', + }, + constraints: false, + }); + + db.notification_logs.belongsTo(db.users, { + as: 'createdBy', + }); + + db.notification_logs.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return notification_logs; +}; diff --git a/backend/src/db/models/roles.js b/backend/src/db/models/roles.js index 0ff5736..10fcd6c 100644 --- a/backend/src/db/models/roles.js +++ b/backend/src/db/models/roles.js @@ -64,6 +64,14 @@ module.exports = function (sequelize, DataTypes) { constraints: false, }); + db.roles.hasMany(db.employees, { + as: 'employees_role', + foreignKey: { + name: 'roleId', + }, + constraints: false, + }); + //end loop db.roles.belongsTo(db.users, { diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index df0b208..54c3933 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -9,6 +9,8 @@ const PreRegistrations = db.pre_registrations; const Visitors = db.visitors; +const NotificationLogs = db.notification_logs; + const DepartmentsData = [ { name: 'HR', @@ -21,6 +23,14 @@ const DepartmentsData = [ { name: 'Admin', }, + + { + name: 'Security', + }, + + { + name: 'James Clerk Maxwell', + }, ]; const EmployeesData = [ @@ -32,6 +42,10 @@ const EmployeesData = [ phone: '5551234567', // type code here for "relation_one" field + + password_hash: 'Louis Pasteur', + + // type code here for "relation_one" field }, { @@ -42,6 +56,10 @@ const EmployeesData = [ phone: '5552345678', // type code here for "relation_one" field + + password_hash: 'Emil Kraepelin', + + // type code here for "relation_one" field }, { @@ -52,6 +70,38 @@ const EmployeesData = [ phone: '5553456789', // type code here for "relation_one" field + + password_hash: 'Max Born', + + // type code here for "relation_one" field + }, + + { + name: 'David Red', + + email: 'david.red@company.com', + + phone: '5554567890', + + // type code here for "relation_one" field + + password_hash: 'Edward Teller', + + // type code here for "relation_one" field + }, + + { + name: 'Laura Yellow', + + email: 'laura.yellow@company.com', + + phone: '5555678901', + + // type code here for "relation_one" field + + password_hash: 'Isaac Newton', + + // type code here for "relation_one" field }, ]; @@ -61,7 +111,7 @@ const PreRegistrationsData = [ expected_check_in: new Date('2023-10-06T09:00:00Z'), - status: 'pending', + status: 'cancelled', }, { @@ -69,7 +119,7 @@ const PreRegistrationsData = [ expected_check_in: new Date('2023-10-07T10:00:00Z'), - status: 'checked-in', + status: 'cancelled', }, { @@ -77,6 +127,22 @@ const PreRegistrationsData = [ expected_check_in: new Date('2023-10-08T11:00:00Z'), + status: 'checked-in', + }, + + { + // type code here for "relation_one" field + + expected_check_in: new Date('2023-10-09T12:00:00Z'), + + status: 'cancelled', + }, + + { + // type code here for "relation_one" field + + expected_check_in: new Date('2023-10-10T13:00:00Z'), + status: 'cancelled', }, ]; @@ -141,12 +207,78 @@ const VisitorsData = [ check_out_time: new Date('2023-10-03T15:30:00Z'), - status: 'checked-in', + status: 'checked-out', // type code here for "images" field badge_id: 'V12347', }, + + { + full_name: 'Bob Brown', + + email: 'bob.brown@example.com', + + phone: '2233445566', + + purpose: 'Maintenance', + + // type code here for "relation_one" field + + check_in_time: new Date('2023-10-04T08:30:00Z'), + + check_out_time: new Date('2023-10-04T09:30:00Z'), + + status: 'checked-in', + + // type code here for "images" field + + badge_id: 'V12348', + }, + + { + full_name: 'Charlie Green', + + email: 'charlie.green@example.com', + + phone: '3344556677', + + purpose: 'Training', + + // type code here for "relation_one" field + + check_in_time: new Date('2023-10-05T13:00:00Z'), + + check_out_time: new Date('2023-10-05T14:00:00Z'), + + status: 'checked-in', + + // type code here for "images" field + + badge_id: 'V12349', + }, +]; + +const NotificationLogsData = [ + { + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + }, + + { + // type code here for "relation_one" field + }, ]; // Similar logic for "relation_many" @@ -184,6 +316,28 @@ async function associateEmployeeWithDepartment() { if (Employee2?.setDepartment) { await Employee2.setDepartment(relatedDepartment2); } + + const relatedDepartment3 = await Departments.findOne({ + offset: Math.floor(Math.random() * (await Departments.count())), + }); + const Employee3 = await Employees.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Employee3?.setDepartment) { + await Employee3.setDepartment(relatedDepartment3); + } + + const relatedDepartment4 = await Departments.findOne({ + offset: Math.floor(Math.random() * (await Departments.count())), + }); + const Employee4 = await Employees.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Employee4?.setDepartment) { + await Employee4.setDepartment(relatedDepartment4); + } } async function associatePreRegistrationWithVisitor() { @@ -219,6 +373,28 @@ async function associatePreRegistrationWithVisitor() { if (PreRegistration2?.setVisitor) { await PreRegistration2.setVisitor(relatedVisitor2); } + + const relatedVisitor3 = await Visitors.findOne({ + offset: Math.floor(Math.random() * (await Visitors.count())), + }); + const PreRegistration3 = await PreRegistrations.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (PreRegistration3?.setVisitor) { + await PreRegistration3.setVisitor(relatedVisitor3); + } + + const relatedVisitor4 = await Visitors.findOne({ + offset: Math.floor(Math.random() * (await Visitors.count())), + }); + const PreRegistration4 = await PreRegistrations.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (PreRegistration4?.setVisitor) { + await PreRegistration4.setVisitor(relatedVisitor4); + } } async function associateVisitorWithHost() { @@ -254,6 +430,85 @@ async function associateVisitorWithHost() { if (Visitor2?.setHost) { await Visitor2.setHost(relatedHost2); } + + const relatedHost3 = await Employees.findOne({ + offset: Math.floor(Math.random() * (await Employees.count())), + }); + const Visitor3 = await Visitors.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (Visitor3?.setHost) { + await Visitor3.setHost(relatedHost3); + } + + const relatedHost4 = await Employees.findOne({ + offset: Math.floor(Math.random() * (await Employees.count())), + }); + const Visitor4 = await Visitors.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (Visitor4?.setHost) { + await Visitor4.setHost(relatedHost4); + } +} + +async function associateNotificationLogWithEmployee() { + const relatedEmployee0 = await Employees.findOne({ + offset: Math.floor(Math.random() * (await Employees.count())), + }); + const NotificationLog0 = await NotificationLogs.findOne({ + order: [['id', 'ASC']], + offset: 0, + }); + if (NotificationLog0?.setEmployee) { + await NotificationLog0.setEmployee(relatedEmployee0); + } + + const relatedEmployee1 = await Employees.findOne({ + offset: Math.floor(Math.random() * (await Employees.count())), + }); + const NotificationLog1 = await NotificationLogs.findOne({ + order: [['id', 'ASC']], + offset: 1, + }); + if (NotificationLog1?.setEmployee) { + await NotificationLog1.setEmployee(relatedEmployee1); + } + + const relatedEmployee2 = await Employees.findOne({ + offset: Math.floor(Math.random() * (await Employees.count())), + }); + const NotificationLog2 = await NotificationLogs.findOne({ + order: [['id', 'ASC']], + offset: 2, + }); + if (NotificationLog2?.setEmployee) { + await NotificationLog2.setEmployee(relatedEmployee2); + } + + const relatedEmployee3 = await Employees.findOne({ + offset: Math.floor(Math.random() * (await Employees.count())), + }); + const NotificationLog3 = await NotificationLogs.findOne({ + order: [['id', 'ASC']], + offset: 3, + }); + if (NotificationLog3?.setEmployee) { + await NotificationLog3.setEmployee(relatedEmployee3); + } + + const relatedEmployee4 = await Employees.findOne({ + offset: Math.floor(Math.random() * (await Employees.count())), + }); + const NotificationLog4 = await NotificationLogs.findOne({ + order: [['id', 'ASC']], + offset: 4, + }); + if (NotificationLog4?.setEmployee) { + await NotificationLog4.setEmployee(relatedEmployee4); + } } module.exports = { @@ -266,6 +521,8 @@ module.exports = { await Visitors.bulkCreate(VisitorsData); + await NotificationLogs.bulkCreate(NotificationLogsData); + await Promise.all([ // Similar logic for "relation_many" @@ -274,6 +531,8 @@ module.exports = { await associatePreRegistrationWithVisitor(), await associateVisitorWithHost(), + + await associateNotificationLogWithEmployee(), ]); }, @@ -285,5 +544,7 @@ module.exports = { await queryInterface.bulkDelete('pre_registrations', null, {}); await queryInterface.bulkDelete('visitors', null, {}); + + await queryInterface.bulkDelete('notification_logs', null, {}); }, }; diff --git a/backend/src/index.js b/backend/src/index.js index 4bb9641..7239951 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -18,6 +18,7 @@ const pexelsRoutes = require('./routes/pexels'); const openaiRoutes = require('./routes/openai'); const contactFormRoutes = require('./routes/contactForm'); +const staffRoutes = require('./routes/staff'); const usersRoutes = require('./routes/users'); @@ -147,6 +148,7 @@ app.use( ); app.use('/api/contact-form', contactFormRoutes); +app.use('/api/staff', staffRoutes); app.use( '/api/search', diff --git a/backend/src/routes/employees.js b/backend/src/routes/employees.js index c109ba1..c813ae9 100644 --- a/backend/src/routes/employees.js +++ b/backend/src/routes/employees.js @@ -29,6 +29,9 @@ router.use(checkCrudPermissions('employees')); * phone: * type: string * default: phone + * password_hash: + * type: string + * default: password_hash */ @@ -310,7 +313,7 @@ router.get( const currentUser = req.currentUser; const payload = await EmployeesDBApi.findAll(req.query, { currentUser }); if (filetype && filetype === 'csv') { - const fields = ['id', 'name', 'email', 'phone']; + const fields = ['id', 'name', 'email', 'phone', 'password_hash']; const opts = { fields }; try { const csv = parse(payload.rows, opts); diff --git a/backend/src/routes/staff.js b/backend/src/routes/staff.js new file mode 100644 index 0000000..70274db --- /dev/null +++ b/backend/src/routes/staff.js @@ -0,0 +1,97 @@ +const express = require('express'); +const router = express.Router(); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const { Employee } = require('../db/models'); +const { Visitor } = require('../db/models'); +const { NotificationLog } = require('../db/models'); +const authMiddleware = require('../middlewares/auth'); + +// Helper to generate JWT +function generateToken(employee) { + const payload = { id: employee.id, role: employee.role }; + return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '8h' }); +} + +// POST /staff/login +router.post('/login', async (req, res) => { + try { + const { email, password } = req.body; + const employee = await Employee.findOne({ where: { email } }); + if (!employee) return res.status(401).json({ message: 'Invalid credentials' }); + const valid = await bcrypt.compare(password, employee.password_hash); + if (!valid) return res.status(401).json({ message: 'Invalid credentials' }); + const token = generateToken(employee); + res.json({ token, employee: { id: employee.id, name: employee.name, email: employee.email, role: employee.role } }); + } catch (err) { + console.error(err); + res.status(500).json({ message: 'Server error' }); + } +}); + +// Protect below routes +router.use(authMiddleware); + +// GET /staff/visitors +router.get('/visitors', async (req, res) => { + try { + const visitors = await Visitor.findAll({ where: { assigned_employee_id: req.user.id } }); + res.json(visitors); + } catch (err) { + res.status(500).json({ message: 'Server error' }); + } +}); + +// POST /staff/visitors/:id/approve +router.post('/visitors/:id/approve', async (req, res) => { + try { + const visitor = await Visitor.findByPk(req.params.id); + if (!visitor) return res.status(404).json({ message: 'Not found' }); + visitor.status = 'approved'; + await visitor.save(); + // Log notification + await NotificationLog.create({ employee_id: req.user.id, message: `Visitor ${visitor.full_name} approved.` }); + res.json(visitor); + } catch (err) { + res.status(500).json({ message: 'Server error' }); + } +}); + +// POST /staff/visitors/:id/reject +router.post('/visitors/:id/reject', async (req, res) => { + try { + const visitor = await Visitor.findByPk(req.params.id); + if (!visitor) return res.status(404).json({ message: 'Not found' }); + visitor.status = 'rejected'; + await visitor.save(); + await NotificationLog.create({ employee_id: req.user.id, message: `Visitor ${visitor.full_name} rejected.` }); + res.json(visitor); + } catch (err) { + res.status(500).json({ message: 'Server error' }); + } +}); + +// GET /staff/history +router.get('/history', async (req, res) => { + try { + const history = await Visitor.findAll({ + where: { assigned_employee_id: req.user.id, status: ['approved', 'rejected', 'checked-out'] }, + order: [['check_in_time', 'DESC']], + }); + res.json(history); + } catch (err) { + res.status(500).json({ message: 'Server error' }); + } +}); + +// GET /staff/notifications +router.get('/notifications', async (req, res) => { + try { + const notes = await NotificationLog.findAll({ where: { employee_id: req.user.id }, order: [['created_at', 'DESC']], limit: 20 }); + res.json(notes); + } catch (err) { + res.status(500).json({ message: 'Server error' }); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/backend/src/services/search.js b/backend/src/services/search.js index 2019271..40244b7 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -45,7 +45,7 @@ module.exports = class SearchService { departments: ['name'], - employees: ['name', 'email', 'phone'], + employees: ['name', 'email', 'phone', 'password_hash'], visitors: ['full_name', 'email', 'phone', 'purpose', 'badge_id'], }; diff --git a/frontend/json/runtimeError.json b/frontend/json/runtimeError.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/frontend/json/runtimeError.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/frontend/src/components/Employees/CardEmployees.tsx b/frontend/src/components/Employees/CardEmployees.tsx index e183f17..d355144 100644 --- a/frontend/src/components/Employees/CardEmployees.tsx +++ b/frontend/src/components/Employees/CardEmployees.tsx @@ -109,6 +109,26 @@ const CardEmployees = ({ + +
+
+ Password hash +
+
+
+ {item.password_hash} +
+
+
+ +
+
Role
+
+
+ {dataFormatter.rolesOneListFormatter(item.role)} +
+
+
))} diff --git a/frontend/src/components/Employees/ListEmployees.tsx b/frontend/src/components/Employees/ListEmployees.tsx index 1fa508a..2ac7a92 100644 --- a/frontend/src/components/Employees/ListEmployees.tsx +++ b/frontend/src/components/Employees/ListEmployees.tsx @@ -78,6 +78,18 @@ const ListEmployees = ({ )}

+ +
+

Password hash

+

{item.password_hash}

+
+ +
+

Role

+

+ {dataFormatter.rolesOneListFormatter(item.role)} +

+
value?.id, + getOptionLabel: (value: any) => value?.label, + valueOptions: await callOptionsApi('roles'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + { field: 'actions', type: 'actions', diff --git a/frontend/src/components/Notification_logs/CardNotification_logs.tsx b/frontend/src/components/Notification_logs/CardNotification_logs.tsx new file mode 100644 index 0000000..d5f7e57 --- /dev/null +++ b/frontend/src/components/Notification_logs/CardNotification_logs.tsx @@ -0,0 +1,112 @@ +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 = { + notification_logs: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const CardNotification_logs = ({ + notification_logs, + 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_NOTIFICATION_LOGS', + ); + + return ( +
+ {loading && } +
    + {!loading && + notification_logs.map((item, index) => ( +
  • +
    + + {item.id} + + +
    + +
    +
    +
    +
    +
    + Employee +
    +
    +
    + {dataFormatter.employeesOneListFormatter(item.employee)} +
    +
    +
    +
    +
  • + ))} + {!loading && notification_logs.length === 0 && ( +
    +

    No data to display

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

Employee

+

+ {dataFormatter.employeesOneListFormatter(item.employee)} +

+
+ + +
+
+ ))} + {!loading && notification_logs.length === 0 && ( +
+

No data to display

+
+ )} +
+
+ +
+ + ); +}; + +export default ListNotification_logs; diff --git a/frontend/src/components/Notification_logs/configureNotification_logsCols.tsx b/frontend/src/components/Notification_logs/configureNotification_logsCols.tsx new file mode 100644 index 0000000..46cc8dd --- /dev/null +++ b/frontend/src/components/Notification_logs/configureNotification_logsCols.tsx @@ -0,0 +1,81 @@ +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_NOTIFICATION_LOGS'); + + return [ + { + field: 'employee', + headerName: 'Employee', + 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('employees'), + valueGetter: (params: GridValueGetterParams) => + params?.value?.id ?? params?.value, + }, + + { + field: 'actions', + type: 'actions', + minWidth: 30, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + getActions: (params: GridRowParams) => { + return [ + , + ]; + }, + }, + ]; +}; diff --git a/frontend/src/components/WebPageComponents/Footer.tsx b/frontend/src/components/WebPageComponents/Footer.tsx index 78d5af7..3833078 100644 --- a/frontend/src/components/WebPageComponents/Footer.tsx +++ b/frontend/src/components/WebPageComponents/Footer.tsx @@ -18,9 +18,9 @@ export default function WebSiteFooter({ 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 (
{ Email Phone + + Password hash @@ -93,6 +95,10 @@ const DepartmentsView = () => { {item.email} {item.phone} + + + {item.password_hash} + ))} diff --git a/frontend/src/pages/employees/[employeesId].tsx b/frontend/src/pages/employees/[employeesId].tsx index 50df45e..67e1883 100644 --- a/frontend/src/pages/employees/[employeesId].tsx +++ b/frontend/src/pages/employees/[employeesId].tsx @@ -43,6 +43,10 @@ const EditEmployees = () => { phone: '', department: null, + + password_hash: '', + + role: null, }; const [initialValues, setInitialValues] = useState(initVals); @@ -120,6 +124,21 @@ const EditEmployees = () => { > + + + + + + + + diff --git a/frontend/src/pages/employees/employees-edit.tsx b/frontend/src/pages/employees/employees-edit.tsx index caa2be3..f9db2af 100644 --- a/frontend/src/pages/employees/employees-edit.tsx +++ b/frontend/src/pages/employees/employees-edit.tsx @@ -43,6 +43,10 @@ const EditEmployeesPage = () => { phone: '', department: null, + + password_hash: '', + + role: null, }; const [initialValues, setInitialValues] = useState(initVals); @@ -118,6 +122,21 @@ const EditEmployeesPage = () => { > + + + + + + + + diff --git a/frontend/src/pages/employees/employees-list.tsx b/frontend/src/pages/employees/employees-list.tsx index 8026be2..70a9cfe 100644 --- a/frontend/src/pages/employees/employees-list.tsx +++ b/frontend/src/pages/employees/employees-list.tsx @@ -32,8 +32,11 @@ const EmployeesTablesPage = () => { { label: 'Name', title: 'name' }, { label: 'Email', title: 'email' }, { label: 'Phone', title: 'phone' }, + { label: 'Password hash', title: 'password_hash' }, { label: 'Department', title: 'department' }, + + { label: 'Role', title: 'role' }, ]); const hasCreatePermission = diff --git a/frontend/src/pages/employees/employees-new.tsx b/frontend/src/pages/employees/employees-new.tsx index afd095d..25d7675 100644 --- a/frontend/src/pages/employees/employees-new.tsx +++ b/frontend/src/pages/employees/employees-new.tsx @@ -40,6 +40,10 @@ const initialValues = { phone: '', department: '', + + password_hash: '', + + role: '', }; const EmployeesNew = () => { @@ -91,6 +95,20 @@ const EmployeesNew = () => { > + + + + + + + + diff --git a/frontend/src/pages/employees/employees-table.tsx b/frontend/src/pages/employees/employees-table.tsx index 4f90d6a..d0b291a 100644 --- a/frontend/src/pages/employees/employees-table.tsx +++ b/frontend/src/pages/employees/employees-table.tsx @@ -32,8 +32,11 @@ const EmployeesTablesPage = () => { { label: 'Name', title: 'name' }, { label: 'Email', title: 'email' }, { label: 'Phone', title: 'phone' }, + { label: 'Password hash', title: 'password_hash' }, { label: 'Department', title: 'department' }, + + { label: 'Role', title: 'role' }, ]); const hasCreatePermission = diff --git a/frontend/src/pages/employees/employees-view.tsx b/frontend/src/pages/employees/employees-view.tsx index 9b46524..02bdf03 100644 --- a/frontend/src/pages/employees/employees-view.tsx +++ b/frontend/src/pages/employees/employees-view.tsx @@ -75,6 +75,17 @@ const EmployeesView = () => {

{employees?.department?.name ?? 'No data'}

+
+

Password hash

+

{employees?.password_hash}

+
+ +
+

Role

+ +

{employees?.role?.name ?? 'No data'}

+
+ <>

Visitors Host

{ + <> +

Notification_logs Employee

+ +
+ + + + + + {employees.notification_logs_employee && + Array.isArray(employees.notification_logs_employee) && + employees.notification_logs_employee.map((item: any) => ( + + router.push( + `/notification_logs/notification_logs-view/?id=${item.id}`, + ) + } + > + ))} + +
+
+ {!employees?.notification_logs_employee?.length && ( +
No data
+ )} +
+ + { diff --git a/frontend/src/pages/notification_logs/[notification_logsId].tsx b/frontend/src/pages/notification_logs/[notification_logsId].tsx new file mode 100644 index 0000000..e00a562 --- /dev/null +++ b/frontend/src/pages/notification_logs/[notification_logsId].tsx @@ -0,0 +1,140 @@ +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/notification_logs/notification_logsSlice'; +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 EditNotification_logs = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + employee: null, + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { notification_logs } = useAppSelector( + (state) => state.notification_logs, + ); + + const { notification_logsId } = router.query; + + useEffect(() => { + dispatch(fetch({ id: notification_logsId })); + }, [notification_logsId]); + + useEffect(() => { + if (typeof notification_logs === 'object') { + setInitialValues(notification_logs); + } + }, [notification_logs]); + + useEffect(() => { + if (typeof notification_logs === 'object') { + const newInitialVal = { ...initVals }; + + Object.keys(initVals).forEach( + (el) => (newInitialVal[el] = notification_logs[el]), + ); + + setInitialValues(newInitialVal); + } + }, [notification_logs]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: notification_logsId, data })); + await router.push('/notification_logs/notification_logs-list'); + }; + + return ( + <> + + {getPageTitle('Edit notification_logs')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + router.push('/notification_logs/notification_logs-list') + } + /> + + +
+
+
+ + ); +}; + +EditNotification_logs.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default EditNotification_logs; diff --git a/frontend/src/pages/notification_logs/notification_logs-edit.tsx b/frontend/src/pages/notification_logs/notification_logs-edit.tsx new file mode 100644 index 0000000..8683561 --- /dev/null +++ b/frontend/src/pages/notification_logs/notification_logs-edit.tsx @@ -0,0 +1,138 @@ +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/notification_logs/notification_logsSlice'; +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 EditNotification_logsPage = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + employee: null, + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { notification_logs } = useAppSelector( + (state) => state.notification_logs, + ); + + const { id } = router.query; + + useEffect(() => { + dispatch(fetch({ id: id })); + }, [id]); + + useEffect(() => { + if (typeof notification_logs === 'object') { + setInitialValues(notification_logs); + } + }, [notification_logs]); + + useEffect(() => { + if (typeof notification_logs === 'object') { + const newInitialVal = { ...initVals }; + Object.keys(initVals).forEach( + (el) => (newInitialVal[el] = notification_logs[el]), + ); + setInitialValues(newInitialVal); + } + }, [notification_logs]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: id, data })); + await router.push('/notification_logs/notification_logs-list'); + }; + + return ( + <> + + {getPageTitle('Edit notification_logs')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + router.push('/notification_logs/notification_logs-list') + } + /> + + +
+
+
+ + ); +}; + +EditNotification_logsPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default EditNotification_logsPage; diff --git a/frontend/src/pages/notification_logs/notification_logs-list.tsx b/frontend/src/pages/notification_logs/notification_logs-list.tsx new file mode 100644 index 0000000..ded2c0b --- /dev/null +++ b/frontend/src/pages/notification_logs/notification_logs-list.tsx @@ -0,0 +1,165 @@ +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 TableNotification_logs from '../../components/Notification_logs/TableNotification_logs'; +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/notification_logs/notification_logsSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const Notification_logsTablesPage = () => { + const [filterItems, setFilterItems] = useState([]); + const [csvFile, setCsvFile] = useState(null); + const [isModalActive, setIsModalActive] = useState(false); + const [showTableView, setShowTableView] = useState(false); + + const { currentUser } = useAppSelector((state) => state.auth); + + const dispatch = useAppDispatch(); + + const [filters] = useState([{ label: 'Employee', title: 'employee' }]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_NOTIFICATION_LOGS'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getNotification_logsCSV = async () => { + const response = await axios({ + url: '/notification_logs?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 = 'notification_logsCSV.csv'; + link.click(); + }; + + const onModalConfirm = async () => { + if (!csvFile) return; + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; + + return ( + <> + + {getPageTitle('Notification_logs')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + + +
+ + + + + ); +}; + +Notification_logsTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default Notification_logsTablesPage; diff --git a/frontend/src/pages/notification_logs/notification_logs-new.tsx b/frontend/src/pages/notification_logs/notification_logs-new.tsx new file mode 100644 index 0000000..5bc7823 --- /dev/null +++ b/frontend/src/pages/notification_logs/notification_logs-new.tsx @@ -0,0 +1,106 @@ +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/notification_logs/notification_logsSlice'; +import { useAppDispatch } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import moment from 'moment'; + +const initialValues = { + employee: '', +}; + +const Notification_logsNew = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + + const handleSubmit = async (data) => { + await dispatch(create(data)); + await router.push('/notification_logs/notification_logs-list'); + }; + return ( + <> + + {getPageTitle('New Item')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + + router.push('/notification_logs/notification_logs-list') + } + /> + + +
+
+
+ + ); +}; + +Notification_logsNew.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default Notification_logsNew; diff --git a/frontend/src/pages/notification_logs/notification_logs-table.tsx b/frontend/src/pages/notification_logs/notification_logs-table.tsx new file mode 100644 index 0000000..475d5ff --- /dev/null +++ b/frontend/src/pages/notification_logs/notification_logs-table.tsx @@ -0,0 +1,164 @@ +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 TableNotification_logs from '../../components/Notification_logs/TableNotification_logs'; +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/notification_logs/notification_logsSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const Notification_logsTablesPage = () => { + const [filterItems, setFilterItems] = useState([]); + const [csvFile, setCsvFile] = useState(null); + const [isModalActive, setIsModalActive] = useState(false); + const [showTableView, setShowTableView] = useState(false); + + const { currentUser } = useAppSelector((state) => state.auth); + + const dispatch = useAppDispatch(); + + const [filters] = useState([{ label: 'Employee', title: 'employee' }]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_NOTIFICATION_LOGS'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getNotification_logsCSV = async () => { + const response = await axios({ + url: '/notification_logs?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 = 'notification_logsCSV.csv'; + link.click(); + }; + + const onModalConfirm = async () => { + if (!csvFile) return; + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; + + return ( + <> + + {getPageTitle('Notification_logs')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + +
+ + + + + ); +}; + +Notification_logsTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default Notification_logsTablesPage; diff --git a/frontend/src/pages/notification_logs/notification_logs-view.tsx b/frontend/src/pages/notification_logs/notification_logs-view.tsx new file mode 100644 index 0000000..3b25eee --- /dev/null +++ b/frontend/src/pages/notification_logs/notification_logs-view.tsx @@ -0,0 +1,88 @@ +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/notification_logs/notification_logsSlice'; +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 Notification_logsView = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const { notification_logs } = useAppSelector( + (state) => state.notification_logs, + ); + + const { id } = router.query; + + function removeLastCharacter(str) { + console.log(str, `str`); + return str.slice(0, -1); + } + + useEffect(() => { + dispatch(fetch({ id })); + }, [dispatch, id]); + + return ( + <> + + {getPageTitle('View notification_logs')} + + + + + + +
+

Employee

+ +

{notification_logs?.employee?.name ?? 'No data'}

+
+ + + + + router.push('/notification_logs/notification_logs-list') + } + /> +
+
+ + ); +}; + +Notification_logsView.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default Notification_logsView; diff --git a/frontend/src/pages/roles/roles-view.tsx b/frontend/src/pages/roles/roles-view.tsx index b7368ff..683fd3f 100644 --- a/frontend/src/pages/roles/roles-view.tsx +++ b/frontend/src/pages/roles/roles-view.tsx @@ -149,6 +149,57 @@ const RolesView = () => { + <> +

Employees Role

+ +
+ + + + + + + + + + + + + + {roles.employees_role && + Array.isArray(roles.employees_role) && + roles.employees_role.map((item: any) => ( + + router.push( + `/employees/employees-view/?id=${item.id}`, + ) + } + > + + + + + + + + + ))} + +
NameEmailPhonePassword hash
{item.name}{item.email}{item.phone} + {item.password_hash} +
+
+ {!roles?.employees_role?.length && ( +
No data
+ )} +
+ + { + e.preventDefault(); + setLoading(true); + setError(''); + try { + const resp = await axios.post('/api/staff/login', { email, password }); + const { token } = resp.data; + localStorage.setItem('staff-token', token); + router.push('/staff/dashboard'); + } catch (err) { + setError(err.response?.data?.message || 'Login failed'); + } + setLoading(false); + }; + + return ( +
+
+

Staff Login

+ {error &&

{error}

} +
+
+ + setEmail(e.target.value)} + className="w-full border border-gray-300 p-2 rounded" + required + /> +
+
+ + setPassword(e.target.value)} + className="w-full border border-gray-300 p-2 rounded" + required + /> +
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/web_pages/home.tsx b/frontend/src/pages/web_pages/home.tsx index e265cdd..765183e 100644 --- a/frontend/src/pages/web_pages/home.tsx +++ b/frontend/src/pages/web_pages/home.tsx @@ -144,7 +144,7 @@ export default function WebSite() { diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 08055fc..42e3607 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -40,6 +40,16 @@ module.exports = { 'fade-in': 'fade-in 250ms ease-in-out', }, colors: { + primary: 'var(--color-primary)', + secondary: 'var(--color-secondary)', + success: 'var(--color-success)', + danger: 'var(--color-danger)', + warning: 'var(--color-warning)', + gray: { + light: 'var(--color-gray-light)', + lighter: 'var(--color-gray-lighter)', + }, + dark: { 900: '#131618', 800: '#21242A',