01
This commit is contained in:
parent
d130092f78
commit
25962a2c05
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,3 +1,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
*/node_modules/
|
*/node_modules/
|
||||||
*/build/
|
*/build/
|
||||||
|
|
||||||
|
**/node_modules/
|
||||||
|
**/build/
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
File diff suppressed because one or more lines are too long
@ -18,6 +18,7 @@ module.exports = class EmployeesDBApi {
|
|||||||
name: data.name || null,
|
name: data.name || null,
|
||||||
email: data.email || null,
|
email: data.email || null,
|
||||||
phone: data.phone || null,
|
phone: data.phone || null,
|
||||||
|
password_hash: data.password_hash || null,
|
||||||
importHash: data.importHash || null,
|
importHash: data.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
updatedById: currentUser.id,
|
updatedById: currentUser.id,
|
||||||
@ -29,6 +30,10 @@ module.exports = class EmployeesDBApi {
|
|||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await employees.setRole(data.role || null, {
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
return employees;
|
return employees;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +48,7 @@ module.exports = class EmployeesDBApi {
|
|||||||
name: item.name || null,
|
name: item.name || null,
|
||||||
email: item.email || null,
|
email: item.email || null,
|
||||||
phone: item.phone || null,
|
phone: item.phone || null,
|
||||||
|
password_hash: item.password_hash || null,
|
||||||
importHash: item.importHash || null,
|
importHash: item.importHash || null,
|
||||||
createdById: currentUser.id,
|
createdById: currentUser.id,
|
||||||
updatedById: currentUser.id,
|
updatedById: currentUser.id,
|
||||||
@ -73,6 +79,9 @@ module.exports = class EmployeesDBApi {
|
|||||||
|
|
||||||
if (data.phone !== undefined) updatePayload.phone = data.phone;
|
if (data.phone !== undefined) updatePayload.phone = data.phone;
|
||||||
|
|
||||||
|
if (data.password_hash !== undefined)
|
||||||
|
updatePayload.password_hash = data.password_hash;
|
||||||
|
|
||||||
updatePayload.updatedById = currentUser.id;
|
updatePayload.updatedById = currentUser.id;
|
||||||
|
|
||||||
await employees.update(updatePayload, { transaction });
|
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;
|
return employees;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,10 +167,19 @@ module.exports = class EmployeesDBApi {
|
|||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
output.notification_logs_employee =
|
||||||
|
await employees.getNotification_logs_employee({
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
output.department = await employees.getDepartment({
|
output.department = await employees.getDepartment({
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
output.role = await employees.getRole({
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
return output;
|
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) {
|
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) {
|
if (filter.active !== undefined) {
|
||||||
where = {
|
where = {
|
||||||
...where,
|
...where,
|
||||||
|
|||||||
289
backend/src/db/api/notification_logs.js
Normal file
289
backend/src/db/api/notification_logs.js
Normal file
@ -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,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -141,6 +141,10 @@ module.exports = class RolesDBApi {
|
|||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
output.employees_role = await roles.getEmployees_role({
|
||||||
|
transaction,
|
||||||
|
});
|
||||||
|
|
||||||
output.permissions = await roles.getPermissions({
|
output.permissions = await roles.getPermissions({
|
||||||
transaction,
|
transaction,
|
||||||
});
|
});
|
||||||
|
|||||||
49
backend/src/db/migrations/1746445548702.js
Normal file
49
backend/src/db/migrations/1746445548702.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* @param {QueryInterface} queryInterface
|
||||||
|
* @param {Sequelize} Sequelize
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
/**
|
||||||
|
* @type {Transaction}
|
||||||
|
*/
|
||||||
|
const transaction = await queryInterface.sequelize.transaction();
|
||||||
|
try {
|
||||||
|
await queryInterface.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<void>}
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
52
backend/src/db/migrations/1746445584795.js
Normal file
52
backend/src/db/migrations/1746445584795.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* @param {QueryInterface} queryInterface
|
||||||
|
* @param {Sequelize} Sequelize
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
/**
|
||||||
|
* @type {Transaction}
|
||||||
|
*/
|
||||||
|
const transaction = await queryInterface.sequelize.transaction();
|
||||||
|
try {
|
||||||
|
await queryInterface.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<void>}
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
54
backend/src/db/migrations/1746445630402.js
Normal file
54
backend/src/db/migrations/1746445630402.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* @param {QueryInterface} queryInterface
|
||||||
|
* @param {Sequelize} Sequelize
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async up(queryInterface, Sequelize) {
|
||||||
|
/**
|
||||||
|
* @type {Transaction}
|
||||||
|
*/
|
||||||
|
const transaction = await queryInterface.sequelize.transaction();
|
||||||
|
try {
|
||||||
|
await queryInterface.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<void>}
|
||||||
|
*/
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -26,6 +26,10 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
type: DataTypes.TEXT,
|
type: DataTypes.TEXT,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
password_hash: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
},
|
||||||
|
|
||||||
importHash: {
|
importHash: {
|
||||||
type: DataTypes.STRING(255),
|
type: DataTypes.STRING(255),
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
@ -50,6 +54,14 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
constraints: false,
|
constraints: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.employees.hasMany(db.notification_logs, {
|
||||||
|
as: 'notification_logs_employee',
|
||||||
|
foreignKey: {
|
||||||
|
name: 'employeeId',
|
||||||
|
},
|
||||||
|
constraints: false,
|
||||||
|
});
|
||||||
|
|
||||||
//end loop
|
//end loop
|
||||||
|
|
||||||
db.employees.belongsTo(db.departments, {
|
db.employees.belongsTo(db.departments, {
|
||||||
@ -60,6 +72,14 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
constraints: false,
|
constraints: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.employees.belongsTo(db.roles, {
|
||||||
|
as: 'role',
|
||||||
|
foreignKey: {
|
||||||
|
name: 'roleId',
|
||||||
|
},
|
||||||
|
constraints: false,
|
||||||
|
});
|
||||||
|
|
||||||
db.employees.belongsTo(db.users, {
|
db.employees.belongsTo(db.users, {
|
||||||
as: 'createdBy',
|
as: 'createdBy',
|
||||||
});
|
});
|
||||||
|
|||||||
53
backend/src/db/models/notification_logs.js
Normal file
53
backend/src/db/models/notification_logs.js
Normal file
@ -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;
|
||||||
|
};
|
||||||
@ -64,6 +64,14 @@ module.exports = function (sequelize, DataTypes) {
|
|||||||
constraints: false,
|
constraints: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
db.roles.hasMany(db.employees, {
|
||||||
|
as: 'employees_role',
|
||||||
|
foreignKey: {
|
||||||
|
name: 'roleId',
|
||||||
|
},
|
||||||
|
constraints: false,
|
||||||
|
});
|
||||||
|
|
||||||
//end loop
|
//end loop
|
||||||
|
|
||||||
db.roles.belongsTo(db.users, {
|
db.roles.belongsTo(db.users, {
|
||||||
|
|||||||
@ -9,6 +9,8 @@ const PreRegistrations = db.pre_registrations;
|
|||||||
|
|
||||||
const Visitors = db.visitors;
|
const Visitors = db.visitors;
|
||||||
|
|
||||||
|
const NotificationLogs = db.notification_logs;
|
||||||
|
|
||||||
const DepartmentsData = [
|
const DepartmentsData = [
|
||||||
{
|
{
|
||||||
name: 'HR',
|
name: 'HR',
|
||||||
@ -21,6 +23,14 @@ const DepartmentsData = [
|
|||||||
{
|
{
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'Security',
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'James Clerk Maxwell',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const EmployeesData = [
|
const EmployeesData = [
|
||||||
@ -32,6 +42,10 @@ const EmployeesData = [
|
|||||||
phone: '5551234567',
|
phone: '5551234567',
|
||||||
|
|
||||||
// type code here for "relation_one" field
|
// 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',
|
phone: '5552345678',
|
||||||
|
|
||||||
// type code here for "relation_one" field
|
// 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',
|
phone: '5553456789',
|
||||||
|
|
||||||
// type code here for "relation_one" field
|
// 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'),
|
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'),
|
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'),
|
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',
|
status: 'cancelled',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@ -141,12 +207,78 @@ const VisitorsData = [
|
|||||||
|
|
||||||
check_out_time: new Date('2023-10-03T15:30:00Z'),
|
check_out_time: new Date('2023-10-03T15:30:00Z'),
|
||||||
|
|
||||||
status: 'checked-in',
|
status: 'checked-out',
|
||||||
|
|
||||||
// type code here for "images" field
|
// type code here for "images" field
|
||||||
|
|
||||||
badge_id: 'V12347',
|
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"
|
// Similar logic for "relation_many"
|
||||||
@ -184,6 +316,28 @@ async function associateEmployeeWithDepartment() {
|
|||||||
if (Employee2?.setDepartment) {
|
if (Employee2?.setDepartment) {
|
||||||
await Employee2.setDepartment(relatedDepartment2);
|
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() {
|
async function associatePreRegistrationWithVisitor() {
|
||||||
@ -219,6 +373,28 @@ async function associatePreRegistrationWithVisitor() {
|
|||||||
if (PreRegistration2?.setVisitor) {
|
if (PreRegistration2?.setVisitor) {
|
||||||
await PreRegistration2.setVisitor(relatedVisitor2);
|
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() {
|
async function associateVisitorWithHost() {
|
||||||
@ -254,6 +430,85 @@ async function associateVisitorWithHost() {
|
|||||||
if (Visitor2?.setHost) {
|
if (Visitor2?.setHost) {
|
||||||
await Visitor2.setHost(relatedHost2);
|
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 = {
|
module.exports = {
|
||||||
@ -266,6 +521,8 @@ module.exports = {
|
|||||||
|
|
||||||
await Visitors.bulkCreate(VisitorsData);
|
await Visitors.bulkCreate(VisitorsData);
|
||||||
|
|
||||||
|
await NotificationLogs.bulkCreate(NotificationLogsData);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
// Similar logic for "relation_many"
|
// Similar logic for "relation_many"
|
||||||
|
|
||||||
@ -274,6 +531,8 @@ module.exports = {
|
|||||||
await associatePreRegistrationWithVisitor(),
|
await associatePreRegistrationWithVisitor(),
|
||||||
|
|
||||||
await associateVisitorWithHost(),
|
await associateVisitorWithHost(),
|
||||||
|
|
||||||
|
await associateNotificationLogWithEmployee(),
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -285,5 +544,7 @@ module.exports = {
|
|||||||
await queryInterface.bulkDelete('pre_registrations', null, {});
|
await queryInterface.bulkDelete('pre_registrations', null, {});
|
||||||
|
|
||||||
await queryInterface.bulkDelete('visitors', null, {});
|
await queryInterface.bulkDelete('visitors', null, {});
|
||||||
|
|
||||||
|
await queryInterface.bulkDelete('notification_logs', null, {});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,6 +18,7 @@ const pexelsRoutes = require('./routes/pexels');
|
|||||||
const openaiRoutes = require('./routes/openai');
|
const openaiRoutes = require('./routes/openai');
|
||||||
|
|
||||||
const contactFormRoutes = require('./routes/contactForm');
|
const contactFormRoutes = require('./routes/contactForm');
|
||||||
|
const staffRoutes = require('./routes/staff');
|
||||||
|
|
||||||
const usersRoutes = require('./routes/users');
|
const usersRoutes = require('./routes/users');
|
||||||
|
|
||||||
@ -147,6 +148,7 @@ app.use(
|
|||||||
);
|
);
|
||||||
|
|
||||||
app.use('/api/contact-form', contactFormRoutes);
|
app.use('/api/contact-form', contactFormRoutes);
|
||||||
|
app.use('/api/staff', staffRoutes);
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
'/api/search',
|
'/api/search',
|
||||||
|
|||||||
@ -29,6 +29,9 @@ router.use(checkCrudPermissions('employees'));
|
|||||||
* phone:
|
* phone:
|
||||||
* type: string
|
* type: string
|
||||||
* default: phone
|
* default: phone
|
||||||
|
* password_hash:
|
||||||
|
* type: string
|
||||||
|
* default: password_hash
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -310,7 +313,7 @@ router.get(
|
|||||||
const currentUser = req.currentUser;
|
const currentUser = req.currentUser;
|
||||||
const payload = await EmployeesDBApi.findAll(req.query, { currentUser });
|
const payload = await EmployeesDBApi.findAll(req.query, { currentUser });
|
||||||
if (filetype && filetype === 'csv') {
|
if (filetype && filetype === 'csv') {
|
||||||
const fields = ['id', 'name', 'email', 'phone'];
|
const fields = ['id', 'name', 'email', 'phone', 'password_hash'];
|
||||||
const opts = { fields };
|
const opts = { fields };
|
||||||
try {
|
try {
|
||||||
const csv = parse(payload.rows, opts);
|
const csv = parse(payload.rows, opts);
|
||||||
|
|||||||
97
backend/src/routes/staff.js
Normal file
97
backend/src/routes/staff.js
Normal file
@ -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;
|
||||||
@ -45,7 +45,7 @@ module.exports = class SearchService {
|
|||||||
|
|
||||||
departments: ['name'],
|
departments: ['name'],
|
||||||
|
|
||||||
employees: ['name', 'email', 'phone'],
|
employees: ['name', 'email', 'phone', 'password_hash'],
|
||||||
|
|
||||||
visitors: ['full_name', 'email', 'phone', 'purpose', 'badge_id'],
|
visitors: ['full_name', 'email', 'phone', 'purpose', 'badge_id'],
|
||||||
};
|
};
|
||||||
|
|||||||
1
frontend/json/runtimeError.json
Normal file
1
frontend/json/runtimeError.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
@ -109,6 +109,26 @@ const CardEmployees = ({
|
|||||||
</div>
|
</div>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
|
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||||
|
Password hash
|
||||||
|
</dt>
|
||||||
|
<dd className='flex items-start gap-x-2'>
|
||||||
|
<div className='font-medium line-clamp-4'>
|
||||||
|
{item.password_hash}
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
|
<dt className=' text-gray-500 dark:text-dark-600'>Role</dt>
|
||||||
|
<dd className='flex items-start gap-x-2'>
|
||||||
|
<div className='font-medium line-clamp-4'>
|
||||||
|
{dataFormatter.rolesOneListFormatter(item.role)}
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
</dl>
|
</dl>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -78,6 +78,18 @@ const ListEmployees = ({
|
|||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex-1 px-3'}>
|
||||||
|
<p className={'text-xs text-gray-500 '}>Password hash</p>
|
||||||
|
<p className={'line-clamp-2'}>{item.password_hash}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'flex-1 px-3'}>
|
||||||
|
<p className={'text-xs text-gray-500 '}>Role</p>
|
||||||
|
<p className={'line-clamp-2'}>
|
||||||
|
{dataFormatter.rolesOneListFormatter(item.role)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<ListActionsPopover
|
<ListActionsPopover
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
|
|||||||
@ -94,6 +94,38 @@ export const loadColumns = async (
|
|||||||
params?.value?.id ?? params?.value,
|
params?.value?.id ?? params?.value,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'password_hash',
|
||||||
|
headerName: 'Password hash',
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 120,
|
||||||
|
filterable: false,
|
||||||
|
headerClassName: 'datagrid--header',
|
||||||
|
cellClassName: 'datagrid--cell',
|
||||||
|
|
||||||
|
editable: hasUpdatePermission,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'role',
|
||||||
|
headerName: 'Role',
|
||||||
|
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('roles'),
|
||||||
|
valueGetter: (params: GridValueGetterParams) =>
|
||||||
|
params?.value?.id ?? params?.value,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: 'actions',
|
||||||
type: 'actions',
|
type: 'actions',
|
||||||
|
|||||||
@ -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 (
|
||||||
|
<div className={'p-4'}>
|
||||||
|
{loading && <LoadingSpinner />}
|
||||||
|
<ul
|
||||||
|
role='list'
|
||||||
|
className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8'
|
||||||
|
>
|
||||||
|
{!loading &&
|
||||||
|
notification_logs.map((item, index) => (
|
||||||
|
<li
|
||||||
|
key={item.id}
|
||||||
|
className={`overflow-hidden ${
|
||||||
|
corners !== 'rounded-full' ? corners : 'rounded-3xl'
|
||||||
|
} border ${focusRing} border-gray-200 dark:border-dark-700 ${
|
||||||
|
darkMode ? 'aside-scrollbars-[slate]' : asideScrollbarsStyle
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`flex items-center ${bgColor} p-6 gap-x-4 border-b border-gray-900/5 bg-gray-50 dark:bg-dark-800 relative`}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={`/notification_logs/notification_logs-view/?id=${item.id}`}
|
||||||
|
className='text-lg font-bold leading-6 line-clamp-1'
|
||||||
|
>
|
||||||
|
{item.id}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className='ml-auto '>
|
||||||
|
<ListActionsPopover
|
||||||
|
onDelete={onDelete}
|
||||||
|
itemId={item.id}
|
||||||
|
pathEdit={`/notification_logs/notification_logs-edit/?id=${item.id}`}
|
||||||
|
pathView={`/notification_logs/notification_logs-view/?id=${item.id}`}
|
||||||
|
hasUpdatePermission={hasUpdatePermission}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<dl className='divide-y divide-stone-300 dark:divide-dark-700 px-6 py-4 text-sm leading-6 h-64 overflow-y-auto'>
|
||||||
|
<div className='flex justify-between gap-x-4 py-3'>
|
||||||
|
<dt className=' text-gray-500 dark:text-dark-600'>
|
||||||
|
Employee
|
||||||
|
</dt>
|
||||||
|
<dd className='flex items-start gap-x-2'>
|
||||||
|
<div className='font-medium line-clamp-4'>
|
||||||
|
{dataFormatter.employeesOneListFormatter(item.employee)}
|
||||||
|
</div>
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{!loading && notification_logs.length === 0 && (
|
||||||
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
|
<p className=''>No data to display</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
<div className={'flex items-center justify-center my-6'}>
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
numPages={numPages}
|
||||||
|
setCurrentPage={onPageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CardNotification_logs;
|
||||||
@ -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 (
|
||||||
|
<>
|
||||||
|
<div className='relative overflow-x-auto p-4 space-y-4'>
|
||||||
|
{loading && <LoadingSpinner />}
|
||||||
|
{!loading &&
|
||||||
|
notification_logs.map((item) => (
|
||||||
|
<CardBox
|
||||||
|
hasTable
|
||||||
|
isList
|
||||||
|
key={item.id}
|
||||||
|
className={'rounded shadow-none'}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`flex rounded dark:bg-dark-900 border border-stone-300 items-center overflow-hidden`}
|
||||||
|
>
|
||||||
|
<Link
|
||||||
|
href={`/notification_logs/notification_logs-view/?id=${item.id}`}
|
||||||
|
className={
|
||||||
|
'flex-1 px-4 py-6 h-24 flex divide-x-2 divide-stone-300 items-center overflow-hidden`}> dark:divide-dark-700 overflow-x-auto'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className={'flex-1 px-3'}>
|
||||||
|
<p className={'text-xs text-gray-500 '}>Employee</p>
|
||||||
|
<p className={'line-clamp-2'}>
|
||||||
|
{dataFormatter.employeesOneListFormatter(item.employee)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
<ListActionsPopover
|
||||||
|
onDelete={onDelete}
|
||||||
|
itemId={item.id}
|
||||||
|
pathEdit={`/notification_logs/notification_logs-edit/?id=${item.id}`}
|
||||||
|
pathView={`/notification_logs/notification_logs-view/?id=${item.id}`}
|
||||||
|
hasUpdatePermission={hasUpdatePermission}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
))}
|
||||||
|
{!loading && notification_logs.length === 0 && (
|
||||||
|
<div className='col-span-full flex items-center justify-center h-40'>
|
||||||
|
<p className=''>No data to display</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className={'flex items-center justify-center my-6'}>
|
||||||
|
<Pagination
|
||||||
|
currentPage={currentPage}
|
||||||
|
numPages={numPages}
|
||||||
|
setCurrentPage={onPageChange}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListNotification_logs;
|
||||||
@ -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 [
|
||||||
|
<ListActionsPopover
|
||||||
|
onDelete={onDelete}
|
||||||
|
itemId={params?.row?.id}
|
||||||
|
pathEdit={`/notification_logs/notification_logs-edit/?id=${params?.row?.id}`}
|
||||||
|
pathView={`/notification_logs/notification_logs-view/?id=${params?.row?.id}`}
|
||||||
|
key={1}
|
||||||
|
hasUpdatePermission={hasUpdatePermission}
|
||||||
|
/>,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
@ -18,9 +18,9 @@ export default function WebSiteFooter({
|
|||||||
const borders = useAppSelector((state) => state.style.borders);
|
const borders = useAppSelector((state) => state.style.borders);
|
||||||
const websiteHeder = useAppSelector((state) => state.style.websiteHeder);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -1,3 +1,15 @@
|
|||||||
|
/* Theme variables */
|
||||||
|
:root {
|
||||||
|
--color-primary: #007BFF;
|
||||||
|
--color-secondary: #6C757D;
|
||||||
|
--color-success: #28A745;
|
||||||
|
--color-danger: #DC3545;
|
||||||
|
--color-warning: #FFC107;
|
||||||
|
--color-gray-light: #F8F9FA;
|
||||||
|
--color-gray-lighter: #E9ECEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.theme-pink {
|
.theme-pink {
|
||||||
.app-sidebar {
|
.app-sidebar {
|
||||||
@apply bg-pavitra-900 text-white;
|
@apply bg-pavitra-900 text-white;
|
||||||
|
|||||||
@ -71,7 +71,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const title = 'VMS';
|
const title = 'Aspect Visiter Management System Demo';
|
||||||
|
|
||||||
const description = 'VMS generated by Flatlogic';
|
const description = 'VMS generated by Flatlogic';
|
||||||
|
|
||||||
|
|||||||
@ -74,6 +74,8 @@ const DepartmentsView = () => {
|
|||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
|
|
||||||
<th>Phone</th>
|
<th>Phone</th>
|
||||||
|
|
||||||
|
<th>Password hash</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -93,6 +95,10 @@ const DepartmentsView = () => {
|
|||||||
<td data-label='email'>{item.email}</td>
|
<td data-label='email'>{item.email}</td>
|
||||||
|
|
||||||
<td data-label='phone'>{item.phone}</td>
|
<td data-label='phone'>{item.phone}</td>
|
||||||
|
|
||||||
|
<td data-label='password_hash'>
|
||||||
|
{item.password_hash}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -43,6 +43,10 @@ const EditEmployees = () => {
|
|||||||
phone: '',
|
phone: '',
|
||||||
|
|
||||||
department: null,
|
department: null,
|
||||||
|
|
||||||
|
password_hash: '',
|
||||||
|
|
||||||
|
role: null,
|
||||||
};
|
};
|
||||||
const [initialValues, setInitialValues] = useState(initVals);
|
const [initialValues, setInitialValues] = useState(initVals);
|
||||||
|
|
||||||
@ -120,6 +124,21 @@ const EditEmployees = () => {
|
|||||||
></Field>
|
></Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
<FormField label='Password hash'>
|
||||||
|
<Field name='password_hash' placeholder='Password hash' />
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField label='Role' labelFor='role'>
|
||||||
|
<Field
|
||||||
|
name='role'
|
||||||
|
id='role'
|
||||||
|
component={SelectField}
|
||||||
|
options={initialValues.role}
|
||||||
|
itemRef={'roles'}
|
||||||
|
showField={'name'}
|
||||||
|
></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type='submit' color='info' label='Submit' />
|
<BaseButton type='submit' color='info' label='Submit' />
|
||||||
|
|||||||
@ -43,6 +43,10 @@ const EditEmployeesPage = () => {
|
|||||||
phone: '',
|
phone: '',
|
||||||
|
|
||||||
department: null,
|
department: null,
|
||||||
|
|
||||||
|
password_hash: '',
|
||||||
|
|
||||||
|
role: null,
|
||||||
};
|
};
|
||||||
const [initialValues, setInitialValues] = useState(initVals);
|
const [initialValues, setInitialValues] = useState(initVals);
|
||||||
|
|
||||||
@ -118,6 +122,21 @@ const EditEmployeesPage = () => {
|
|||||||
></Field>
|
></Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
<FormField label='Password hash'>
|
||||||
|
<Field name='password_hash' placeholder='Password hash' />
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField label='Role' labelFor='role'>
|
||||||
|
<Field
|
||||||
|
name='role'
|
||||||
|
id='role'
|
||||||
|
component={SelectField}
|
||||||
|
options={initialValues.role}
|
||||||
|
itemRef={'roles'}
|
||||||
|
showField={'name'}
|
||||||
|
></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type='submit' color='info' label='Submit' />
|
<BaseButton type='submit' color='info' label='Submit' />
|
||||||
|
|||||||
@ -32,8 +32,11 @@ const EmployeesTablesPage = () => {
|
|||||||
{ label: 'Name', title: 'name' },
|
{ label: 'Name', title: 'name' },
|
||||||
{ label: 'Email', title: 'email' },
|
{ label: 'Email', title: 'email' },
|
||||||
{ label: 'Phone', title: 'phone' },
|
{ label: 'Phone', title: 'phone' },
|
||||||
|
{ label: 'Password hash', title: 'password_hash' },
|
||||||
|
|
||||||
{ label: 'Department', title: 'department' },
|
{ label: 'Department', title: 'department' },
|
||||||
|
|
||||||
|
{ label: 'Role', title: 'role' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hasCreatePermission =
|
const hasCreatePermission =
|
||||||
|
|||||||
@ -40,6 +40,10 @@ const initialValues = {
|
|||||||
phone: '',
|
phone: '',
|
||||||
|
|
||||||
department: '',
|
department: '',
|
||||||
|
|
||||||
|
password_hash: '',
|
||||||
|
|
||||||
|
role: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmployeesNew = () => {
|
const EmployeesNew = () => {
|
||||||
@ -91,6 +95,20 @@ const EmployeesNew = () => {
|
|||||||
></Field>
|
></Field>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
|
||||||
|
<FormField label='Password hash'>
|
||||||
|
<Field name='password_hash' placeholder='Password hash' />
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField label='Role' labelFor='role'>
|
||||||
|
<Field
|
||||||
|
name='role'
|
||||||
|
id='role'
|
||||||
|
component={SelectField}
|
||||||
|
options={[]}
|
||||||
|
itemRef={'roles'}
|
||||||
|
></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
<BaseButtons>
|
<BaseButtons>
|
||||||
<BaseButton type='submit' color='info' label='Submit' />
|
<BaseButton type='submit' color='info' label='Submit' />
|
||||||
|
|||||||
@ -32,8 +32,11 @@ const EmployeesTablesPage = () => {
|
|||||||
{ label: 'Name', title: 'name' },
|
{ label: 'Name', title: 'name' },
|
||||||
{ label: 'Email', title: 'email' },
|
{ label: 'Email', title: 'email' },
|
||||||
{ label: 'Phone', title: 'phone' },
|
{ label: 'Phone', title: 'phone' },
|
||||||
|
{ label: 'Password hash', title: 'password_hash' },
|
||||||
|
|
||||||
{ label: 'Department', title: 'department' },
|
{ label: 'Department', title: 'department' },
|
||||||
|
|
||||||
|
{ label: 'Role', title: 'role' },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hasCreatePermission =
|
const hasCreatePermission =
|
||||||
|
|||||||
@ -75,6 +75,17 @@ const EmployeesView = () => {
|
|||||||
<p>{employees?.department?.name ?? 'No data'}</p>
|
<p>{employees?.department?.name ?? 'No data'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className={'mb-4'}>
|
||||||
|
<p className={'block font-bold mb-2'}>Password hash</p>
|
||||||
|
<p>{employees?.password_hash}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={'mb-4'}>
|
||||||
|
<p className={'block font-bold mb-2'}>Role</p>
|
||||||
|
|
||||||
|
<p>{employees?.role?.name ?? 'No data'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<>
|
<>
|
||||||
<p className={'block font-bold mb-2'}>Visitors Host</p>
|
<p className={'block font-bold mb-2'}>Visitors Host</p>
|
||||||
<CardBox
|
<CardBox
|
||||||
@ -148,6 +159,39 @@ const EmployeesView = () => {
|
|||||||
</CardBox>
|
</CardBox>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
<>
|
||||||
|
<p className={'block font-bold mb-2'}>Notification_logs Employee</p>
|
||||||
|
<CardBox
|
||||||
|
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||||
|
hasTable
|
||||||
|
>
|
||||||
|
<div className='overflow-x-auto'>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{employees.notification_logs_employee &&
|
||||||
|
Array.isArray(employees.notification_logs_employee) &&
|
||||||
|
employees.notification_logs_employee.map((item: any) => (
|
||||||
|
<tr
|
||||||
|
key={item.id}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/notification_logs/notification_logs-view/?id=${item.id}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
></tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{!employees?.notification_logs_employee?.length && (
|
||||||
|
<div className={'text-center py-4'}>No data</div>
|
||||||
|
)}
|
||||||
|
</CardBox>
|
||||||
|
</>
|
||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export default function Login() {
|
|||||||
remember: true,
|
remember: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const title = 'VMS';
|
const title = 'Aspect Visiter Management System Demo';
|
||||||
|
|
||||||
// Fetch Pexels image/video
|
// Fetch Pexels image/video
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
140
frontend/src/pages/notification_logs/[notification_logsId].tsx
Normal file
140
frontend/src/pages/notification_logs/[notification_logsId].tsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('Edit notification_logs')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton
|
||||||
|
icon={mdiChartTimelineVariant}
|
||||||
|
title={'Edit notification_logs'}
|
||||||
|
main
|
||||||
|
>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
<CardBox>
|
||||||
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={(values) => handleSubmit(values)}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<FormField label='Employee' labelFor='employee'>
|
||||||
|
<Field
|
||||||
|
name='employee'
|
||||||
|
id='employee'
|
||||||
|
component={SelectField}
|
||||||
|
options={initialValues.employee}
|
||||||
|
itemRef={'employees'}
|
||||||
|
showField={'name'}
|
||||||
|
></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<BaseDivider />
|
||||||
|
<BaseButtons>
|
||||||
|
<BaseButton type='submit' color='info' label='Submit' />
|
||||||
|
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||||
|
<BaseButton
|
||||||
|
type='reset'
|
||||||
|
color='danger'
|
||||||
|
outline
|
||||||
|
label='Cancel'
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/notification_logs/notification_logs-list')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</BaseButtons>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</CardBox>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
EditNotification_logs.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return (
|
||||||
|
<LayoutAuthenticated permission={'UPDATE_NOTIFICATION_LOGS'}>
|
||||||
|
{page}
|
||||||
|
</LayoutAuthenticated>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditNotification_logs;
|
||||||
138
frontend/src/pages/notification_logs/notification_logs-edit.tsx
Normal file
138
frontend/src/pages/notification_logs/notification_logs-edit.tsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('Edit notification_logs')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton
|
||||||
|
icon={mdiChartTimelineVariant}
|
||||||
|
title={'Edit notification_logs'}
|
||||||
|
main
|
||||||
|
>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
<CardBox>
|
||||||
|
<Formik
|
||||||
|
enableReinitialize
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={(values) => handleSubmit(values)}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<FormField label='Employee' labelFor='employee'>
|
||||||
|
<Field
|
||||||
|
name='employee'
|
||||||
|
id='employee'
|
||||||
|
component={SelectField}
|
||||||
|
options={initialValues.employee}
|
||||||
|
itemRef={'employees'}
|
||||||
|
showField={'name'}
|
||||||
|
></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<BaseDivider />
|
||||||
|
<BaseButtons>
|
||||||
|
<BaseButton type='submit' color='info' label='Submit' />
|
||||||
|
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||||
|
<BaseButton
|
||||||
|
type='reset'
|
||||||
|
color='danger'
|
||||||
|
outline
|
||||||
|
label='Cancel'
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/notification_logs/notification_logs-list')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</BaseButtons>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</CardBox>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
EditNotification_logsPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return (
|
||||||
|
<LayoutAuthenticated permission={'UPDATE_NOTIFICATION_LOGS'}>
|
||||||
|
{page}
|
||||||
|
</LayoutAuthenticated>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditNotification_logsPage;
|
||||||
165
frontend/src/pages/notification_logs/notification_logs-list.tsx
Normal file
165
frontend/src/pages/notification_logs/notification_logs-list.tsx
Normal file
@ -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<File | null>(null);
|
||||||
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
|
const [showTableView, setShowTableView] = useState(false);
|
||||||
|
|
||||||
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const [filters] = useState([{ label: '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 (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('Notification_logs')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton
|
||||||
|
icon={mdiChartTimelineVariant}
|
||||||
|
title='Notification_logs'
|
||||||
|
main
|
||||||
|
>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
{hasCreatePermission && (
|
||||||
|
<BaseButton
|
||||||
|
className={'mr-3'}
|
||||||
|
href={'/notification_logs/notification_logs-new'}
|
||||||
|
color='info'
|
||||||
|
label='New Item'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<BaseButton
|
||||||
|
className={'mr-3'}
|
||||||
|
color='info'
|
||||||
|
label='Filter'
|
||||||
|
onClick={addFilter}
|
||||||
|
/>
|
||||||
|
<BaseButton
|
||||||
|
className={'mr-3'}
|
||||||
|
color='info'
|
||||||
|
label='Download CSV'
|
||||||
|
onClick={getNotification_logsCSV}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hasCreatePermission && (
|
||||||
|
<BaseButton
|
||||||
|
color='info'
|
||||||
|
label='Upload CSV'
|
||||||
|
onClick={() => setIsModalActive(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='md:inline-flex items-center ms-auto'>
|
||||||
|
<div id='delete-rows-button'></div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
|
||||||
|
<CardBox className='mb-6' hasTable>
|
||||||
|
<TableNotification_logs
|
||||||
|
filterItems={filterItems}
|
||||||
|
setFilterItems={setFilterItems}
|
||||||
|
filters={filters}
|
||||||
|
showGrid={false}
|
||||||
|
/>
|
||||||
|
</CardBox>
|
||||||
|
</SectionMain>
|
||||||
|
<CardBoxModal
|
||||||
|
title='Upload CSV'
|
||||||
|
buttonColor='info'
|
||||||
|
buttonLabel={'Confirm'}
|
||||||
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
|
isActive={isModalActive}
|
||||||
|
onConfirm={onModalConfirm}
|
||||||
|
onCancel={onModalCancel}
|
||||||
|
>
|
||||||
|
<DragDropFilePicker
|
||||||
|
file={csvFile}
|
||||||
|
setFile={setCsvFile}
|
||||||
|
formats={'.csv'}
|
||||||
|
/>
|
||||||
|
</CardBoxModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Notification_logsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return (
|
||||||
|
<LayoutAuthenticated permission={'READ_NOTIFICATION_LOGS'}>
|
||||||
|
{page}
|
||||||
|
</LayoutAuthenticated>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification_logsTablesPage;
|
||||||
106
frontend/src/pages/notification_logs/notification_logs-new.tsx
Normal file
106
frontend/src/pages/notification_logs/notification_logs-new.tsx
Normal file
@ -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 (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('New Item')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton
|
||||||
|
icon={mdiChartTimelineVariant}
|
||||||
|
title='New Item'
|
||||||
|
main
|
||||||
|
>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
<CardBox>
|
||||||
|
<Formik
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={(values) => handleSubmit(values)}
|
||||||
|
>
|
||||||
|
<Form>
|
||||||
|
<FormField label='Employee' labelFor='employee'>
|
||||||
|
<Field
|
||||||
|
name='employee'
|
||||||
|
id='employee'
|
||||||
|
component={SelectField}
|
||||||
|
options={[]}
|
||||||
|
itemRef={'employees'}
|
||||||
|
></Field>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<BaseDivider />
|
||||||
|
<BaseButtons>
|
||||||
|
<BaseButton type='submit' color='info' label='Submit' />
|
||||||
|
<BaseButton type='reset' color='info' outline label='Reset' />
|
||||||
|
<BaseButton
|
||||||
|
type='reset'
|
||||||
|
color='danger'
|
||||||
|
outline
|
||||||
|
label='Cancel'
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/notification_logs/notification_logs-list')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</BaseButtons>
|
||||||
|
</Form>
|
||||||
|
</Formik>
|
||||||
|
</CardBox>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Notification_logsNew.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return (
|
||||||
|
<LayoutAuthenticated permission={'CREATE_NOTIFICATION_LOGS'}>
|
||||||
|
{page}
|
||||||
|
</LayoutAuthenticated>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification_logsNew;
|
||||||
164
frontend/src/pages/notification_logs/notification_logs-table.tsx
Normal file
164
frontend/src/pages/notification_logs/notification_logs-table.tsx
Normal file
@ -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<File | null>(null);
|
||||||
|
const [isModalActive, setIsModalActive] = useState(false);
|
||||||
|
const [showTableView, setShowTableView] = useState(false);
|
||||||
|
|
||||||
|
const { currentUser } = useAppSelector((state) => state.auth);
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const [filters] = useState([{ label: '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 (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('Notification_logs')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton
|
||||||
|
icon={mdiChartTimelineVariant}
|
||||||
|
title='Notification_logs'
|
||||||
|
main
|
||||||
|
>
|
||||||
|
{''}
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
<CardBox className='mb-6' cardBoxClassName='flex flex-wrap'>
|
||||||
|
{hasCreatePermission && (
|
||||||
|
<BaseButton
|
||||||
|
className={'mr-3'}
|
||||||
|
href={'/notification_logs/notification_logs-new'}
|
||||||
|
color='info'
|
||||||
|
label='New Item'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<BaseButton
|
||||||
|
className={'mr-3'}
|
||||||
|
color='info'
|
||||||
|
label='Filter'
|
||||||
|
onClick={addFilter}
|
||||||
|
/>
|
||||||
|
<BaseButton
|
||||||
|
className={'mr-3'}
|
||||||
|
color='info'
|
||||||
|
label='Download CSV'
|
||||||
|
onClick={getNotification_logsCSV}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{hasCreatePermission && (
|
||||||
|
<BaseButton
|
||||||
|
color='info'
|
||||||
|
label='Upload CSV'
|
||||||
|
onClick={() => setIsModalActive(true)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className='md:inline-flex items-center ms-auto'>
|
||||||
|
<div id='delete-rows-button'></div>
|
||||||
|
</div>
|
||||||
|
</CardBox>
|
||||||
|
<CardBox className='mb-6' hasTable>
|
||||||
|
<TableNotification_logs
|
||||||
|
filterItems={filterItems}
|
||||||
|
setFilterItems={setFilterItems}
|
||||||
|
filters={filters}
|
||||||
|
showGrid={true}
|
||||||
|
/>
|
||||||
|
</CardBox>
|
||||||
|
</SectionMain>
|
||||||
|
<CardBoxModal
|
||||||
|
title='Upload CSV'
|
||||||
|
buttonColor='info'
|
||||||
|
buttonLabel={'Confirm'}
|
||||||
|
// buttonLabel={false ? 'Deleting...' : 'Confirm'}
|
||||||
|
isActive={isModalActive}
|
||||||
|
onConfirm={onModalConfirm}
|
||||||
|
onCancel={onModalCancel}
|
||||||
|
>
|
||||||
|
<DragDropFilePicker
|
||||||
|
file={csvFile}
|
||||||
|
setFile={setCsvFile}
|
||||||
|
formats={'.csv'}
|
||||||
|
/>
|
||||||
|
</CardBoxModal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Notification_logsTablesPage.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return (
|
||||||
|
<LayoutAuthenticated permission={'READ_NOTIFICATION_LOGS'}>
|
||||||
|
{page}
|
||||||
|
</LayoutAuthenticated>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification_logsTablesPage;
|
||||||
@ -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 (
|
||||||
|
<>
|
||||||
|
<Head>
|
||||||
|
<title>{getPageTitle('View notification_logs')}</title>
|
||||||
|
</Head>
|
||||||
|
<SectionMain>
|
||||||
|
<SectionTitleLineWithButton
|
||||||
|
icon={mdiChartTimelineVariant}
|
||||||
|
title={removeLastCharacter('View notification_logs')}
|
||||||
|
main
|
||||||
|
>
|
||||||
|
<BaseButton
|
||||||
|
color='info'
|
||||||
|
label='Edit'
|
||||||
|
href={`/notification_logs/notification_logs-edit/?id=${id}`}
|
||||||
|
/>
|
||||||
|
</SectionTitleLineWithButton>
|
||||||
|
<CardBox>
|
||||||
|
<div className={'mb-4'}>
|
||||||
|
<p className={'block font-bold mb-2'}>Employee</p>
|
||||||
|
|
||||||
|
<p>{notification_logs?.employee?.name ?? 'No data'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BaseDivider />
|
||||||
|
|
||||||
|
<BaseButton
|
||||||
|
color='info'
|
||||||
|
label='Back'
|
||||||
|
onClick={() =>
|
||||||
|
router.push('/notification_logs/notification_logs-list')
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</CardBox>
|
||||||
|
</SectionMain>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Notification_logsView.getLayout = function getLayout(page: ReactElement) {
|
||||||
|
return (
|
||||||
|
<LayoutAuthenticated permission={'READ_NOTIFICATION_LOGS'}>
|
||||||
|
{page}
|
||||||
|
</LayoutAuthenticated>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Notification_logsView;
|
||||||
@ -149,6 +149,57 @@ const RolesView = () => {
|
|||||||
</CardBox>
|
</CardBox>
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
<>
|
||||||
|
<p className={'block font-bold mb-2'}>Employees Role</p>
|
||||||
|
<CardBox
|
||||||
|
className='mb-6 border border-gray-300 rounded overflow-hidden'
|
||||||
|
hasTable
|
||||||
|
>
|
||||||
|
<div className='overflow-x-auto'>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
|
||||||
|
<th>Email</th>
|
||||||
|
|
||||||
|
<th>Phone</th>
|
||||||
|
|
||||||
|
<th>Password hash</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{roles.employees_role &&
|
||||||
|
Array.isArray(roles.employees_role) &&
|
||||||
|
roles.employees_role.map((item: any) => (
|
||||||
|
<tr
|
||||||
|
key={item.id}
|
||||||
|
onClick={() =>
|
||||||
|
router.push(
|
||||||
|
`/employees/employees-view/?id=${item.id}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<td data-label='name'>{item.name}</td>
|
||||||
|
|
||||||
|
<td data-label='email'>{item.email}</td>
|
||||||
|
|
||||||
|
<td data-label='phone'>{item.phone}</td>
|
||||||
|
|
||||||
|
<td data-label='password_hash'>
|
||||||
|
{item.password_hash}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{!roles?.employees_role?.length && (
|
||||||
|
<div className={'text-center py-4'}>No data</div>
|
||||||
|
)}
|
||||||
|
</CardBox>
|
||||||
|
</>
|
||||||
|
|
||||||
<BaseDivider />
|
<BaseDivider />
|
||||||
|
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
|||||||
64
frontend/src/pages/staff-login.tsx
Normal file
64
frontend/src/pages/staff-login.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { useRouter } from 'next/router';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export default function StaffLogin() {
|
||||||
|
const router = useRouter();
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-100">
|
||||||
|
<div className="bg-white p-8 rounded shadow-md w-full max-w-md">
|
||||||
|
<h2 className="text-2xl font-semibold mb-6">Staff Login</h2>
|
||||||
|
{error && <p className="text-red-600 mb-4">{error}</p>}
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium mb-1">Email</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
className="w-full border border-gray-300 p-2 rounded"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium mb-1">Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="w-full border border-gray-300 p-2 rounded"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700 disabled:opacity-50"
|
||||||
|
>
|
||||||
|
{loading ? 'Logging in...' : 'Login'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -144,7 +144,7 @@ export default function WebSite() {
|
|||||||
<FeaturesSection
|
<FeaturesSection
|
||||||
projectName={'VMS'}
|
projectName={'VMS'}
|
||||||
image={['Dashboard with visitor statistics']}
|
image={['Dashboard with visitor statistics']}
|
||||||
withBg={0}
|
withBg={1}
|
||||||
features={features_points}
|
features={features_points}
|
||||||
mainText={`Discover Key Features of ${projectName}`}
|
mainText={`Discover Key Features of ${projectName}`}
|
||||||
subTitle={`Enhance your visitor management with our innovative features designed to streamline operations and improve security.`}
|
subTitle={`Enhance your visitor management with our innovative features designed to streamline operations and improve security.`}
|
||||||
|
|||||||
@ -159,7 +159,7 @@ export default function WebSite() {
|
|||||||
|
|
||||||
<PricingSection
|
<PricingSection
|
||||||
projectName={'VMS'}
|
projectName={'VMS'}
|
||||||
withBg={0}
|
withBg={1}
|
||||||
features={pricing_features}
|
features={pricing_features}
|
||||||
description={description}
|
description={description}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -40,6 +40,16 @@ module.exports = {
|
|||||||
'fade-in': 'fade-in 250ms ease-in-out',
|
'fade-in': 'fade-in 250ms ease-in-out',
|
||||||
},
|
},
|
||||||
colors: {
|
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: {
|
dark: {
|
||||||
900: '#131618',
|
900: '#131618',
|
||||||
800: '#21242A',
|
800: '#21242A',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user