From 25962a2c059f2a433318c1a3052431bf3d5ca64d Mon Sep 17 00:00:00 2001
From: Flatlogic Bot
Date: Mon, 5 May 2025 12:12:09 +0000
Subject: [PATCH] 01
---
.gitignore | 5 +
app-shell/src/_schema.json | 7 +-
backend/src/db/api/employees.js | 63 ++++
backend/src/db/api/notification_logs.js | 289 ++++++++++++++++++
backend/src/db/api/roles.js | 4 +
backend/src/db/migrations/1746445548702.js | 49 +++
backend/src/db/migrations/1746445584795.js | 52 ++++
backend/src/db/migrations/1746445630402.js | 54 ++++
backend/src/db/models/employees.js | 20 ++
backend/src/db/models/notification_logs.js | 53 ++++
backend/src/db/models/roles.js | 8 +
.../db/seeders/20231127130745-sample-data.js | 267 +++++++++++++++-
backend/src/index.js | 2 +
backend/src/routes/employees.js | 5 +-
backend/src/routes/staff.js | 97 ++++++
backend/src/services/search.js | 2 +-
frontend/json/runtimeError.json | 1 +
.../components/Employees/CardEmployees.tsx | 20 ++
.../components/Employees/ListEmployees.tsx | 12 +
.../Employees/configureEmployeesCols.tsx | 32 ++
.../CardNotification_logs.tsx | 112 +++++++
.../ListNotification_logs.tsx | 95 ++++++
.../configureNotification_logsCols.tsx | 81 +++++
.../components/WebPageComponents/Footer.tsx | 4 +-
frontend/src/css/_theme.css | 12 +
frontend/src/pages/_app.tsx | 2 +-
.../pages/departments/departments-view.tsx | 6 +
.../src/pages/employees/[employeesId].tsx | 19 ++
.../src/pages/employees/employees-edit.tsx | 19 ++
.../src/pages/employees/employees-list.tsx | 3 +
.../src/pages/employees/employees-new.tsx | 18 ++
.../src/pages/employees/employees-table.tsx | 3 +
.../src/pages/employees/employees-view.tsx | 44 +++
frontend/src/pages/login.tsx | 2 +-
.../[notification_logsId].tsx | 140 +++++++++
.../notification_logs-edit.tsx | 138 +++++++++
.../notification_logs-list.tsx | 165 ++++++++++
.../notification_logs-new.tsx | 106 +++++++
.../notification_logs-table.tsx | 164 ++++++++++
.../notification_logs-view.tsx | 88 ++++++
frontend/src/pages/roles/roles-view.tsx | 51 ++++
frontend/src/pages/staff-login.tsx | 64 ++++
frontend/src/pages/web_pages/home.tsx | 2 +-
frontend/src/pages/web_pages/pricing.tsx | 2 +-
frontend/tailwind.config.js | 10 +
45 files changed, 2377 insertions(+), 15 deletions(-)
create mode 100644 backend/src/db/api/notification_logs.js
create mode 100644 backend/src/db/migrations/1746445548702.js
create mode 100644 backend/src/db/migrations/1746445584795.js
create mode 100644 backend/src/db/migrations/1746445630402.js
create mode 100644 backend/src/db/models/notification_logs.js
create mode 100644 backend/src/routes/staff.js
create mode 100644 frontend/json/runtimeError.json
create mode 100644 frontend/src/components/Notification_logs/CardNotification_logs.tsx
create mode 100644 frontend/src/components/Notification_logs/ListNotification_logs.tsx
create mode 100644 frontend/src/components/Notification_logs/configureNotification_logsCols.tsx
create mode 100644 frontend/src/pages/notification_logs/[notification_logsId].tsx
create mode 100644 frontend/src/pages/notification_logs/notification_logs-edit.tsx
create mode 100644 frontend/src/pages/notification_logs/notification_logs-list.tsx
create mode 100644 frontend/src/pages/notification_logs/notification_logs-new.tsx
create mode 100644 frontend/src/pages/notification_logs/notification_logs-table.tsx
create mode 100644 frontend/src/pages/notification_logs/notification_logs-view.tsx
create mode 100644 frontend/src/pages/staff-login.tsx
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 && (
+
+ )}
+
+
+
+ );
+};
+
+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 && (
+
+ )}
+
+
+ >
+ );
+};
+
+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)}
+ >
+
+
+
+
+ >
+ );
+};
+
+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)}
+ >
+
+
+
+
+ >
+ );
+};
+
+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)}
+ >
+
+
+
+
+ >
+ );
+};
+
+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
+
+
+
+
+
+ | Name |
+
+ Email |
+
+ Phone |
+
+ Password hash |
+
+
+
+ {roles.employees_role &&
+ Array.isArray(roles.employees_role) &&
+ roles.employees_role.map((item: any) => (
+
+ router.push(
+ `/employees/employees-view/?id=${item.id}`,
+ )
+ }
+ >
+ | {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}
}
+
+
+
+ );
+}
\ 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',