From 44b1eee1a9be965f6c3c783ae20bbb73ff2d7106 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 2 Jun 2025 15:11:38 +0000 Subject: [PATCH] Auto commit: 2025-06-02T15:11:38.313Z --- .gitignore | 5 + app-shell/src/_schema.json | 7 +- backend/src/db/api/attendances.js | 500 ----------- backend/src/db/api/employees.js | 8 - backend/src/db/api/organizations.js | 24 - backend/src/db/api/payrolls.js | 364 -------- backend/src/db/api/permissions.js | 257 ------ backend/src/db/api/roles.js | 344 -------- backend/src/db/api/users.js | 800 ------------------ backend/src/db/migrations/1748875259190.js | 72 ++ backend/src/db/migrations/1748875298946.js | 72 ++ backend/src/db/migrations/1748875330040.js | 72 ++ backend/src/db/migrations/1748875356202.js | 72 ++ backend/src/db/migrations/1748875413338.js | 72 ++ backend/src/db/migrations/1748875435750.js | 72 ++ backend/src/db/models/attendances.js | 87 -- backend/src/db/models/employees.js | 16 - backend/src/db/models/organizations.js | 40 - backend/src/db/models/payrolls.js | 65 -- backend/src/db/models/permissions.js | 49 -- backend/src/db/models/roles.js | 86 -- backend/src/db/models/users.js | 179 ---- .../db/seeders/20200430130759-admin-user.js | 84 -- .../db/seeders/20200430130760-user-roles.js | 793 ----------------- .../db/seeders/20231127130745-sample-data.js | 623 -------------- backend/src/index.js | 48 -- backend/src/routes/attendances.js | 467 ---------- backend/src/routes/payrolls.js | 452 ---------- backend/src/routes/permissions.js | 442 ---------- backend/src/routes/roles.js | 444 ---------- backend/src/routes/users.js | 458 ---------- backend/src/services/attendances.js | 114 --- backend/src/services/payrolls.js | 114 --- backend/src/services/permissions.js | 114 --- backend/src/services/roles.js | 391 --------- backend/src/services/search.js | 66 +- backend/src/services/users.js | 163 ---- frontend/json/runtimeError.json | 1 + .../Attendances/CardAttendances.tsx | 176 ---- .../Attendances/ListAttendances.tsx | 133 --- .../Attendances/TableAttendances.tsx | 513 ----------- .../Attendances/configureAttendancesCols.tsx | 160 ---- .../src/components/Payrolls/CardPayrolls.tsx | 120 --- .../src/components/Payrolls/ListPayrolls.tsx | 94 -- .../src/components/Payrolls/TablePayrolls.tsx | 494 ----------- .../Payrolls/configurePayrollsCols.tsx | 96 --- .../Permissions/CardPermissions.tsx | 105 --- .../Permissions/ListPermissions.tsx | 87 -- .../Permissions/TablePermissions.tsx | 484 ----------- .../Permissions/configurePermissionsCols.tsx | 74 -- frontend/src/components/Roles/CardRoles.tsx | 129 --- frontend/src/components/Roles/ListRoles.tsx | 105 --- frontend/src/components/Roles/TableRoles.tsx | 481 ----------- .../components/Roles/configureRolesCols.tsx | 107 --- frontend/src/components/Users/CardUsers.tsx | 209 ----- frontend/src/components/Users/ListUsers.tsx | 156 ---- frontend/src/components/Users/TableUsers.tsx | 482 ----------- .../components/Users/configureUsersCols.tsx | 203 ----- frontend/src/helpers/dataFormatter.js | 76 -- frontend/src/menuAside.ts | 57 -- .../src/pages/attendances/[attendancesId].tsx | 226 ----- .../pages/attendances/attendances-edit.tsx | 224 ----- .../pages/attendances/attendances-list.tsx | 177 ---- .../src/pages/attendances/attendances-new.tsx | 181 ---- .../pages/attendances/attendances-table.tsx | 176 ---- .../pages/attendances/attendances-view.tsx | 155 ---- frontend/src/pages/dashboard.tsx | 232 +---- .../src/pages/employees/employees-view.tsx | 90 -- frontend/src/pages/index.tsx | 2 +- .../organizations/organizations-view.tsx | 581 ------------- frontend/src/pages/payrolls/[payrollsId].tsx | 158 ---- frontend/src/pages/payrolls/payrolls-edit.tsx | 156 ---- frontend/src/pages/payrolls/payrolls-list.tsx | 170 ---- frontend/src/pages/payrolls/payrolls-new.tsx | 126 --- .../src/pages/payrolls/payrolls-table.tsx | 169 ---- frontend/src/pages/payrolls/payrolls-view.tsx | 99 --- .../src/pages/permissions/[permissionsId].tsx | 130 --- .../pages/permissions/permissions-edit.tsx | 128 --- .../pages/permissions/permissions-list.tsx | 165 ---- .../src/pages/permissions/permissions-new.tsx | 98 --- .../pages/permissions/permissions-table.tsx | 164 ---- .../pages/permissions/permissions-view.tsx | 87 -- frontend/src/pages/roles/[rolesId].tsx | 151 ---- frontend/src/pages/roles/roles-edit.tsx | 149 ---- frontend/src/pages/roles/roles-list.tsx | 164 ---- frontend/src/pages/roles/roles-new.tsx | 120 --- frontend/src/pages/roles/roles-table.tsx | 163 ---- frontend/src/pages/roles/roles-view.tsx | 183 ---- frontend/src/pages/users/[usersId].tsx | 222 ----- frontend/src/pages/users/users-edit.tsx | 220 ----- frontend/src/pages/users/users-list.tsx | 173 ---- frontend/src/pages/users/users-new.tsx | 183 ---- frontend/src/pages/users/users-table.tsx | 168 ---- frontend/src/pages/users/users-view.tsx | 170 ---- frontend/src/pages/web_pages/home.tsx | 4 +- frontend/src/pages/web_pages/pricing.tsx | 2 +- frontend/src/pages/web_pages/services.tsx | 2 +- .../stores/attendances/attendancesSlice.ts | 241 ------ frontend/src/stores/payrolls/payrollsSlice.ts | 236 ------ .../stores/permissions/permissionsSlice.ts | 241 ------ frontend/src/stores/roles/rolesSlice.ts | 283 ------- frontend/src/stores/store.ts | 12 - frontend/src/stores/users/usersSlice.ts | 236 ------ 103 files changed, 449 insertions(+), 19146 deletions(-) delete mode 100644 backend/src/db/api/attendances.js delete mode 100644 backend/src/db/api/payrolls.js delete mode 100644 backend/src/db/api/permissions.js delete mode 100644 backend/src/db/api/roles.js delete mode 100644 backend/src/db/api/users.js create mode 100644 backend/src/db/migrations/1748875259190.js create mode 100644 backend/src/db/migrations/1748875298946.js create mode 100644 backend/src/db/migrations/1748875330040.js create mode 100644 backend/src/db/migrations/1748875356202.js create mode 100644 backend/src/db/migrations/1748875413338.js create mode 100644 backend/src/db/migrations/1748875435750.js delete mode 100644 backend/src/db/models/attendances.js delete mode 100644 backend/src/db/models/payrolls.js delete mode 100644 backend/src/db/models/permissions.js delete mode 100644 backend/src/db/models/roles.js delete mode 100644 backend/src/db/models/users.js delete mode 100644 backend/src/routes/attendances.js delete mode 100644 backend/src/routes/payrolls.js delete mode 100644 backend/src/routes/permissions.js delete mode 100644 backend/src/routes/roles.js delete mode 100644 backend/src/routes/users.js delete mode 100644 backend/src/services/attendances.js delete mode 100644 backend/src/services/payrolls.js delete mode 100644 backend/src/services/permissions.js delete mode 100644 backend/src/services/roles.js delete mode 100644 backend/src/services/users.js create mode 100644 frontend/json/runtimeError.json delete mode 100644 frontend/src/components/Attendances/CardAttendances.tsx delete mode 100644 frontend/src/components/Attendances/ListAttendances.tsx delete mode 100644 frontend/src/components/Attendances/TableAttendances.tsx delete mode 100644 frontend/src/components/Attendances/configureAttendancesCols.tsx delete mode 100644 frontend/src/components/Payrolls/CardPayrolls.tsx delete mode 100644 frontend/src/components/Payrolls/ListPayrolls.tsx delete mode 100644 frontend/src/components/Payrolls/TablePayrolls.tsx delete mode 100644 frontend/src/components/Payrolls/configurePayrollsCols.tsx delete mode 100644 frontend/src/components/Permissions/CardPermissions.tsx delete mode 100644 frontend/src/components/Permissions/ListPermissions.tsx delete mode 100644 frontend/src/components/Permissions/TablePermissions.tsx delete mode 100644 frontend/src/components/Permissions/configurePermissionsCols.tsx delete mode 100644 frontend/src/components/Roles/CardRoles.tsx delete mode 100644 frontend/src/components/Roles/ListRoles.tsx delete mode 100644 frontend/src/components/Roles/TableRoles.tsx delete mode 100644 frontend/src/components/Roles/configureRolesCols.tsx delete mode 100644 frontend/src/components/Users/CardUsers.tsx delete mode 100644 frontend/src/components/Users/ListUsers.tsx delete mode 100644 frontend/src/components/Users/TableUsers.tsx delete mode 100644 frontend/src/components/Users/configureUsersCols.tsx delete mode 100644 frontend/src/pages/attendances/[attendancesId].tsx delete mode 100644 frontend/src/pages/attendances/attendances-edit.tsx delete mode 100644 frontend/src/pages/attendances/attendances-list.tsx delete mode 100644 frontend/src/pages/attendances/attendances-new.tsx delete mode 100644 frontend/src/pages/attendances/attendances-table.tsx delete mode 100644 frontend/src/pages/attendances/attendances-view.tsx delete mode 100644 frontend/src/pages/payrolls/[payrollsId].tsx delete mode 100644 frontend/src/pages/payrolls/payrolls-edit.tsx delete mode 100644 frontend/src/pages/payrolls/payrolls-list.tsx delete mode 100644 frontend/src/pages/payrolls/payrolls-new.tsx delete mode 100644 frontend/src/pages/payrolls/payrolls-table.tsx delete mode 100644 frontend/src/pages/payrolls/payrolls-view.tsx delete mode 100644 frontend/src/pages/permissions/[permissionsId].tsx delete mode 100644 frontend/src/pages/permissions/permissions-edit.tsx delete mode 100644 frontend/src/pages/permissions/permissions-list.tsx delete mode 100644 frontend/src/pages/permissions/permissions-new.tsx delete mode 100644 frontend/src/pages/permissions/permissions-table.tsx delete mode 100644 frontend/src/pages/permissions/permissions-view.tsx delete mode 100644 frontend/src/pages/roles/[rolesId].tsx delete mode 100644 frontend/src/pages/roles/roles-edit.tsx delete mode 100644 frontend/src/pages/roles/roles-list.tsx delete mode 100644 frontend/src/pages/roles/roles-new.tsx delete mode 100644 frontend/src/pages/roles/roles-table.tsx delete mode 100644 frontend/src/pages/roles/roles-view.tsx delete mode 100644 frontend/src/pages/users/[usersId].tsx delete mode 100644 frontend/src/pages/users/users-edit.tsx delete mode 100644 frontend/src/pages/users/users-list.tsx delete mode 100644 frontend/src/pages/users/users-new.tsx delete mode 100644 frontend/src/pages/users/users-table.tsx delete mode 100644 frontend/src/pages/users/users-view.tsx delete mode 100644 frontend/src/stores/attendances/attendancesSlice.ts delete mode 100644 frontend/src/stores/payrolls/payrollsSlice.ts delete mode 100644 frontend/src/stores/permissions/permissionsSlice.ts delete mode 100644 frontend/src/stores/roles/rolesSlice.ts delete mode 100644 frontend/src/stores/users/usersSlice.ts diff --git a/.gitignore b/.gitignore index e427ff3..d0eb167 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/ */node_modules/ */build/ + +**/node_modules/ +**/build/ +.DS_Store +.env \ No newline at end of file diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json index 856017b..b304f5a 100644 --- a/app-shell/src/_schema.json +++ b/app-shell/src/_schema.json @@ -1,5 +1,4 @@ - - { - "Initial version": "{\"iv\":\"ZqXyZwe5rRgz5+9x\",\"encryptedData\":\"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\"}" -} + "Initial version": "{\"iv\":\"ZqXyZwe5rRgz5+9x\",\"encryptedData\":\"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\"}", + "": "{\"iv\":\"Kg4QEu3x53KvVfKd\",\"encryptedData\":\"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\"}" +} \ No newline at end of file diff --git a/backend/src/db/api/attendances.js b/backend/src/db/api/attendances.js deleted file mode 100644 index 39a31c4..0000000 --- a/backend/src/db/api/attendances.js +++ /dev/null @@ -1,500 +0,0 @@ -const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); -const Utils = require('../utils'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class AttendancesDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const attendances = await db.attendances.create( - { - id: data.id || undefined, - - time_in: data.time_in || null, - time_out: data.time_out || null, - total_hours: data.total_hours || null, - gps_location: data.gps_location || null, - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - await attendances.setEmployee(data.employee || null, { - transaction, - }); - - await attendances.setOrganizations(data.organizations || null, { - transaction, - }); - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.attendances.getTableName(), - belongsToColumn: 'selfie', - belongsToId: attendances.id, - }, - data.selfie, - options, - ); - - return attendances; - } - - 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 attendancesData = data.map((item, index) => ({ - id: item.id || undefined, - - time_in: item.time_in || null, - time_out: item.time_out || null, - total_hours: item.total_hours || null, - gps_location: item.gps_location || null, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const attendances = await db.attendances.bulkCreate(attendancesData, { - transaction, - }); - - // For each item created, replace relation files - - for (let i = 0; i < attendances.length; i++) { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.attendances.getTableName(), - belongsToColumn: 'selfie', - belongsToId: attendances[i].id, - }, - data[i].selfie, - options, - ); - } - - return attendances; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - const globalAccess = currentUser.app_role?.globalAccess; - - const attendances = await db.attendances.findByPk(id, {}, { transaction }); - - const updatePayload = {}; - - if (data.time_in !== undefined) updatePayload.time_in = data.time_in; - - if (data.time_out !== undefined) updatePayload.time_out = data.time_out; - - if (data.total_hours !== undefined) - updatePayload.total_hours = data.total_hours; - - if (data.gps_location !== undefined) - updatePayload.gps_location = data.gps_location; - - updatePayload.updatedById = currentUser.id; - - await attendances.update(updatePayload, { transaction }); - - if (data.employee !== undefined) { - await attendances.setEmployee( - data.employee, - - { transaction }, - ); - } - - if (data.organizations !== undefined) { - await attendances.setOrganizations( - data.organizations, - - { transaction }, - ); - } - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.attendances.getTableName(), - belongsToColumn: 'selfie', - belongsToId: attendances.id, - }, - data.selfie, - options, - ); - - return attendances; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const attendances = await db.attendances.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of attendances) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of attendances) { - await record.destroy({ transaction }); - } - }); - - return attendances; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const attendances = await db.attendances.findByPk(id, options); - - await attendances.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await attendances.destroy({ - transaction, - }); - - return attendances; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const attendances = await db.attendances.findOne( - { where }, - { transaction }, - ); - - if (!attendances) { - return attendances; - } - - const output = attendances.get({ plain: true }); - - output.selfie = await attendances.getSelfie({ - transaction, - }); - - output.employee = await attendances.getEmployee({ - transaction, - }); - - output.organizations = await attendances.getOrganizations({ - transaction, - }); - - return output; - } - - static async findAll(filter, globalAccess, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - const user = (options && options.currentUser) || null; - const userOrganizations = (user && user.organizations?.id) || null; - - if (userOrganizations) { - if (options?.currentUser?.organizationsId) { - where.organizationsId = options.currentUser.organizationsId; - } - } - - 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)), - }, - }, - { - first_name: { - [Op.or]: filter.employee - .split('|') - .map((term) => ({ [Op.iLike]: `%${term}%` })), - }, - }, - ], - } - : {}, - }, - - { - model: db.organizations, - as: 'organizations', - }, - - { - model: db.file, - as: 'selfie', - }, - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.gps_location) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'attendances', - 'gps_location', - filter.gps_location, - ), - }; - } - - if (filter.calendarStart && filter.calendarEnd) { - where = { - ...where, - [Op.or]: [ - { - time_in: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - { - time_out: { - [Op.between]: [filter.calendarStart, filter.calendarEnd], - }, - }, - ], - }; - } - - if (filter.time_inRange) { - const [start, end] = filter.time_inRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - time_in: { - ...where.time_in, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - time_in: { - ...where.time_in, - [Op.lte]: end, - }, - }; - } - } - - if (filter.time_outRange) { - const [start, end] = filter.time_outRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - time_out: { - ...where.time_out, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - time_out: { - ...where.time_out, - [Op.lte]: end, - }, - }; - } - } - - if (filter.total_hoursRange) { - const [start, end] = filter.total_hoursRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - total_hours: { - ...where.total_hours, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - total_hours: { - ...where.total_hours, - [Op.lte]: end, - }, - }; - } - } - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true', - }; - } - - if (filter.organizations) { - const listItems = filter.organizations.split('|').map((item) => { - return Utils.uuid(item); - }); - - where = { - ...where, - organizationsId: { [Op.or]: listItems }, - }; - } - - 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, - }, - }; - } - } - } - - if (globalAccess) { - delete where.organizationsId; - } - - 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.attendances.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, - globalAccess, - organizationId, - ) { - let where = {}; - - if (!globalAccess && organizationId) { - where.organizationId = organizationId; - } - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike('attendances', 'gps_location', query), - ], - }; - } - - const records = await db.attendances.findAll({ - attributes: ['id', 'gps_location'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['gps_location', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.gps_location, - })); - } -}; diff --git a/backend/src/db/api/employees.js b/backend/src/db/api/employees.js index fd4b702..d9c7520 100644 --- a/backend/src/db/api/employees.js +++ b/backend/src/db/api/employees.js @@ -354,14 +354,6 @@ module.exports = class EmployeesDBApi { const output = employees.get({ plain: true }); - output.attendances_employee = await employees.getAttendances_employee({ - transaction, - }); - - output.payrolls_employee = await employees.getPayrolls_employee({ - transaction, - }); - output.photo = await employees.getPhoto({ transaction, }); diff --git a/backend/src/db/api/organizations.js b/backend/src/db/api/organizations.js index 90212fb..8654524 100644 --- a/backend/src/db/api/organizations.js +++ b/backend/src/db/api/organizations.js @@ -134,30 +134,6 @@ module.exports = class OrganizationsDBApi { const output = organizations.get({ plain: true }); - output.users_organizations = await organizations.getUsers_organizations({ - transaction, - }); - - output.attendances_organizations = - await organizations.getAttendances_organizations({ - transaction, - }); - - output.employees_organization = - await organizations.getEmployees_organization({ - transaction, - }); - - output.employees_organizations = - await organizations.getEmployees_organizations({ - transaction, - }); - - output.payrolls_organizations = - await organizations.getPayrolls_organizations({ - transaction, - }); - return output; } diff --git a/backend/src/db/api/payrolls.js b/backend/src/db/api/payrolls.js deleted file mode 100644 index a6de4f0..0000000 --- a/backend/src/db/api/payrolls.js +++ /dev/null @@ -1,364 +0,0 @@ -const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); -const Utils = require('../utils'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class PayrollsDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const payrolls = await db.payrolls.create( - { - id: data.id || undefined, - - total_salary: data.total_salary || null, - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - await payrolls.setEmployee(data.employee || null, { - transaction, - }); - - await payrolls.setOrganizations(data.organizations || null, { - transaction, - }); - - return payrolls; - } - - 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 payrollsData = data.map((item, index) => ({ - id: item.id || undefined, - - total_salary: item.total_salary || null, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const payrolls = await db.payrolls.bulkCreate(payrollsData, { - transaction, - }); - - // For each item created, replace relation files - - return payrolls; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - const globalAccess = currentUser.app_role?.globalAccess; - - const payrolls = await db.payrolls.findByPk(id, {}, { transaction }); - - const updatePayload = {}; - - if (data.total_salary !== undefined) - updatePayload.total_salary = data.total_salary; - - updatePayload.updatedById = currentUser.id; - - await payrolls.update(updatePayload, { transaction }); - - if (data.employee !== undefined) { - await payrolls.setEmployee( - data.employee, - - { transaction }, - ); - } - - if (data.organizations !== undefined) { - await payrolls.setOrganizations( - data.organizations, - - { transaction }, - ); - } - - return payrolls; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const payrolls = await db.payrolls.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of payrolls) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of payrolls) { - await record.destroy({ transaction }); - } - }); - - return payrolls; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const payrolls = await db.payrolls.findByPk(id, options); - - await payrolls.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await payrolls.destroy({ - transaction, - }); - - return payrolls; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const payrolls = await db.payrolls.findOne({ where }, { transaction }); - - if (!payrolls) { - return payrolls; - } - - const output = payrolls.get({ plain: true }); - - output.employee = await payrolls.getEmployee({ - transaction, - }); - - output.organizations = await payrolls.getOrganizations({ - transaction, - }); - - return output; - } - - static async findAll(filter, globalAccess, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - const user = (options && options.currentUser) || null; - const userOrganizations = (user && user.organizations?.id) || null; - - if (userOrganizations) { - if (options?.currentUser?.organizationsId) { - where.organizationsId = options.currentUser.organizationsId; - } - } - - 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)), - }, - }, - { - first_name: { - [Op.or]: filter.employee - .split('|') - .map((term) => ({ [Op.iLike]: `%${term}%` })), - }, - }, - ], - } - : {}, - }, - - { - model: db.organizations, - as: 'organizations', - }, - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.total_salaryRange) { - const [start, end] = filter.total_salaryRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - total_salary: { - ...where.total_salary, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - total_salary: { - ...where.total_salary, - [Op.lte]: end, - }, - }; - } - } - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true', - }; - } - - if (filter.organizations) { - const listItems = filter.organizations.split('|').map((item) => { - return Utils.uuid(item); - }); - - where = { - ...where, - organizationsId: { [Op.or]: listItems }, - }; - } - - 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, - }, - }; - } - } - } - - if (globalAccess) { - delete where.organizationsId; - } - - 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.payrolls.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, - globalAccess, - organizationId, - ) { - let where = {}; - - if (!globalAccess && organizationId) { - where.organizationId = organizationId; - } - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike('payrolls', 'total_salary', query), - ], - }; - } - - const records = await db.payrolls.findAll({ - attributes: ['id', 'total_salary'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['total_salary', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.total_salary, - })); - } -}; diff --git a/backend/src/db/api/permissions.js b/backend/src/db/api/permissions.js deleted file mode 100644 index 2873582..0000000 --- a/backend/src/db/api/permissions.js +++ /dev/null @@ -1,257 +0,0 @@ -const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); -const Utils = require('../utils'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class PermissionsDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const permissions = await db.permissions.create( - { - id: data.id || undefined, - - name: data.name || null, - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - return permissions; - } - - 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 permissionsData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name || null, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const permissions = await db.permissions.bulkCreate(permissionsData, { - transaction, - }); - - // For each item created, replace relation files - - return permissions; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - const globalAccess = currentUser.app_role?.globalAccess; - - const permissions = await db.permissions.findByPk(id, {}, { transaction }); - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - updatePayload.updatedById = currentUser.id; - - await permissions.update(updatePayload, { transaction }); - - return permissions; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const permissions = await db.permissions.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of permissions) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of permissions) { - await record.destroy({ transaction }); - } - }); - - return permissions; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const permissions = await db.permissions.findByPk(id, options); - - await permissions.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await permissions.destroy({ - transaction, - }); - - return permissions; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const permissions = await db.permissions.findOne( - { where }, - { transaction }, - ); - - if (!permissions) { - return permissions; - } - - const output = permissions.get({ plain: true }); - - return output; - } - - static async findAll(filter, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - const user = (options && options.currentUser) || null; - const userOrganizations = (user && user.organizations?.id) || null; - - offset = currentPage * limit; - - const orderBy = null; - - const transaction = (options && options.transaction) || undefined; - - let include = []; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike('permissions', 'name', filter.name), - }; - } - - 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.permissions.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('permissions', 'name', query), - ], - }; - } - - const records = await db.permissions.findAll({ - attributes: ['id', 'name'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } -}; diff --git a/backend/src/db/api/roles.js b/backend/src/db/api/roles.js deleted file mode 100644 index 5202540..0000000 --- a/backend/src/db/api/roles.js +++ /dev/null @@ -1,344 +0,0 @@ -const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); -const Utils = require('../utils'); - -const config = require('../../config'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class RolesDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const roles = await db.roles.create( - { - id: data.id || undefined, - - name: data.name || null, - role_customization: data.role_customization || null, - globalAccess: data.globalAccess || false, - - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - await roles.setPermissions(data.permissions || [], { - transaction, - }); - - return roles; - } - - 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 rolesData = data.map((item, index) => ({ - id: item.id || undefined, - - name: item.name || null, - role_customization: item.role_customization || null, - globalAccess: item.globalAccess || false, - - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const roles = await db.roles.bulkCreate(rolesData, { transaction }); - - // For each item created, replace relation files - - return roles; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - const globalAccess = currentUser.app_role?.globalAccess; - - const roles = await db.roles.findByPk(id, {}, { transaction }); - - const updatePayload = {}; - - if (data.name !== undefined) updatePayload.name = data.name; - - if (data.role_customization !== undefined) - updatePayload.role_customization = data.role_customization; - - if (data.globalAccess !== undefined) - updatePayload.globalAccess = data.globalAccess; - - updatePayload.updatedById = currentUser.id; - - await roles.update(updatePayload, { transaction }); - - if (data.permissions !== undefined) { - await roles.setPermissions(data.permissions, { transaction }); - } - - return roles; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const roles = await db.roles.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of roles) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of roles) { - await record.destroy({ transaction }); - } - }); - - return roles; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const roles = await db.roles.findByPk(id, options); - - await roles.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await roles.destroy({ - transaction, - }); - - return roles; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const roles = await db.roles.findOne({ where }, { transaction }); - - if (!roles) { - return roles; - } - - const output = roles.get({ plain: true }); - - output.users_app_role = await roles.getUsers_app_role({ - transaction, - }); - - output.permissions = await roles.getPermissions({ - transaction, - }); - - return output; - } - - static async findAll(filter, globalAccess, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - const user = (options && options.currentUser) || null; - const userOrganizations = (user && user.organizations?.id) || null; - - offset = currentPage * limit; - - const orderBy = null; - - const transaction = (options && options.transaction) || undefined; - - let include = [ - { - model: db.permissions, - as: 'permissions', - required: false, - }, - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.name) { - where = { - ...where, - [Op.and]: Utils.ilike('roles', 'name', filter.name), - }; - } - - if (filter.role_customization) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'roles', - 'role_customization', - filter.role_customization, - ), - }; - } - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true', - }; - } - - if (filter.globalAccess) { - where = { - ...where, - globalAccess: filter.globalAccess, - }; - } - - if (filter.permissions) { - const searchTerms = filter.permissions.split('|'); - - include = [ - { - model: db.permissions, - as: 'permissions_filter', - required: searchTerms.length > 0, - where: - searchTerms.length > 0 - ? { - [Op.or]: [ - { - id: { - [Op.in]: searchTerms.map((term) => Utils.uuid(term)), - }, - }, - { - name: { - [Op.or]: searchTerms.map((term) => ({ - [Op.iLike]: `%${term}%`, - })), - }, - }, - ], - } - : undefined, - }, - ...include, - ]; - } - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - if (!globalAccess) { - where = { name: { [Op.ne]: config.roles.super_admin } }; - } - - 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.roles.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, globalAccess) { - let where = {}; - - if (!globalAccess) { - where = { name: { [Op.ne]: config.roles.super_admin } }; - } - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike('roles', 'name', query), - ], - }; - } - - const records = await db.roles.findAll({ - attributes: ['id', 'name'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['name', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.name, - })); - } -}; diff --git a/backend/src/db/api/users.js b/backend/src/db/api/users.js deleted file mode 100644 index 9f8c451..0000000 --- a/backend/src/db/api/users.js +++ /dev/null @@ -1,800 +0,0 @@ -const db = require('../models'); -const FileDBApi = require('./file'); -const crypto = require('crypto'); -const Utils = require('../utils'); - -const bcrypt = require('bcrypt'); -const config = require('../../config'); - -const Sequelize = db.Sequelize; -const Op = Sequelize.Op; - -module.exports = class UsersDBApi { - static async create(data, globalAccess, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const users = await db.users.create( - { - id: data.data.id || undefined, - - firstName: data.data.firstName || null, - lastName: data.data.lastName || null, - phoneNumber: data.data.phoneNumber || null, - email: data.data.email || null, - disabled: data.data.disabled || false, - - password: data.data.password || null, - emailVerified: data.data.emailVerified || true, - - emailVerificationToken: data.data.emailVerificationToken || null, - emailVerificationTokenExpiresAt: - data.data.emailVerificationTokenExpiresAt || null, - passwordResetToken: data.data.passwordResetToken || null, - passwordResetTokenExpiresAt: - data.data.passwordResetTokenExpiresAt || null, - provider: data.data.provider || null, - importHash: data.data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - if (!data.data.app_role) { - const role = await db.roles.findOne({ - where: { name: 'User' }, - }); - if (role) { - await users.setApp_role(role, { - transaction, - }); - } - } else { - await users.setApp_role(data.data.app_role || null, { - transaction, - }); - } - - await users.setOrganizations(data.data.organizations || null, { - transaction, - }); - - await users.setCustom_permissions(data.data.custom_permissions || [], { - transaction, - }); - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.users.getTableName(), - belongsToColumn: 'avatar', - belongsToId: users.id, - }, - data.data.avatar, - options, - ); - - return users; - } - - 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 usersData = data.map((item, index) => ({ - id: item.id || undefined, - - firstName: item.firstName || null, - lastName: item.lastName || null, - phoneNumber: item.phoneNumber || null, - email: item.email || null, - disabled: item.disabled || false, - - password: item.password || null, - emailVerified: item.emailVerified || false, - - emailVerificationToken: item.emailVerificationToken || null, - emailVerificationTokenExpiresAt: - item.emailVerificationTokenExpiresAt || null, - passwordResetToken: item.passwordResetToken || null, - passwordResetTokenExpiresAt: item.passwordResetTokenExpiresAt || null, - provider: item.provider || null, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const users = await db.users.bulkCreate(usersData, { transaction }); - - // For each item created, replace relation files - - for (let i = 0; i < users.length; i++) { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.users.getTableName(), - belongsToColumn: 'avatar', - belongsToId: users[i].id, - }, - data[i].avatar, - options, - ); - } - - return users; - } - - static async update(id, data, globalAccess, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const users = await db.users.findByPk(id, {}, { transaction }); - - if (!data?.app_role) { - data.app_role = users?.app_role?.id; - } - if (!data?.custom_permissions) { - data.custom_permissions = users?.custom_permissions?.map( - (item) => item.id, - ); - } - - if (data.password) { - data.password = bcrypt.hashSync(data.password, config.bcrypt.saltRounds); - } else { - data.password = users.password; - } - - const updatePayload = {}; - - if (data.firstName !== undefined) updatePayload.firstName = data.firstName; - - if (data.lastName !== undefined) updatePayload.lastName = data.lastName; - - if (data.phoneNumber !== undefined) - updatePayload.phoneNumber = data.phoneNumber; - - if (data.email !== undefined) updatePayload.email = data.email; - - if (data.disabled !== undefined) updatePayload.disabled = data.disabled; - - if (data.password !== undefined) updatePayload.password = data.password; - - if (data.emailVerified !== undefined) - updatePayload.emailVerified = data.emailVerified; - else updatePayload.emailVerified = true; - - if (data.emailVerificationToken !== undefined) - updatePayload.emailVerificationToken = data.emailVerificationToken; - - if (data.emailVerificationTokenExpiresAt !== undefined) - updatePayload.emailVerificationTokenExpiresAt = - data.emailVerificationTokenExpiresAt; - - if (data.passwordResetToken !== undefined) - updatePayload.passwordResetToken = data.passwordResetToken; - - if (data.passwordResetTokenExpiresAt !== undefined) - updatePayload.passwordResetTokenExpiresAt = - data.passwordResetTokenExpiresAt; - - if (data.provider !== undefined) updatePayload.provider = data.provider; - - updatePayload.updatedById = currentUser.id; - - await users.update(updatePayload, { transaction }); - - if (data.app_role !== undefined) { - await users.setApp_role( - data.app_role, - - { transaction }, - ); - } - - if (data.organizations !== undefined) { - await users.setOrganizations( - data.organizations, - - { transaction }, - ); - } - - if (data.custom_permissions !== undefined) { - await users.setCustom_permissions(data.custom_permissions, { - transaction, - }); - } - - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.users.getTableName(), - belongsToColumn: 'avatar', - belongsToId: users.id, - }, - data.avatar, - options, - ); - - return users; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const users = await db.users.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of users) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of users) { - await record.destroy({ transaction }); - } - }); - - return users; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const users = await db.users.findByPk(id, options); - - await users.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await users.destroy({ - transaction, - }); - - return users; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const users = await db.users.findOne({ where }, { transaction }); - - if (!users) { - return users; - } - - const output = users.get({ plain: true }); - - output.avatar = await users.getAvatar({ - transaction, - }); - - output.app_role = await users.getApp_role({ - transaction, - }); - - if (output.app_role) { - output.app_role_permissions = await output.app_role.getPermissions({ - transaction, - }); - } - - output.custom_permissions = await users.getCustom_permissions({ - transaction, - }); - - output.organizations = await users.getOrganizations({ - transaction, - }); - - return output; - } - - static async findAll(filter, globalAccess, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - const user = (options && options.currentUser) || null; - const userOrganizations = (user && user.organizations?.id) || null; - - if (userOrganizations) { - if (options?.currentUser?.organizationsId) { - where.organizationsId = options.currentUser.organizationsId; - } - } - - offset = currentPage * limit; - - const orderBy = null; - - const transaction = (options && options.transaction) || undefined; - - let include = [ - { - model: db.roles, - as: 'app_role', - - where: filter.app_role - ? { - [Op.or]: [ - { - id: { - [Op.in]: filter.app_role - .split('|') - .map((term) => Utils.uuid(term)), - }, - }, - { - name: { - [Op.or]: filter.app_role - .split('|') - .map((term) => ({ [Op.iLike]: `%${term}%` })), - }, - }, - ], - } - : {}, - }, - - { - model: db.organizations, - as: 'organizations', - }, - - { - model: db.permissions, - as: 'custom_permissions', - required: false, - }, - - { - model: db.file, - as: 'avatar', - }, - ]; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.firstName) { - where = { - ...where, - [Op.and]: Utils.ilike('users', 'firstName', filter.firstName), - }; - } - - if (filter.lastName) { - where = { - ...where, - [Op.and]: Utils.ilike('users', 'lastName', filter.lastName), - }; - } - - if (filter.phoneNumber) { - where = { - ...where, - [Op.and]: Utils.ilike('users', 'phoneNumber', filter.phoneNumber), - }; - } - - if (filter.email) { - where = { - ...where, - [Op.and]: Utils.ilike('users', 'email', filter.email), - }; - } - - if (filter.password) { - where = { - ...where, - [Op.and]: Utils.ilike('users', 'password', filter.password), - }; - } - - if (filter.emailVerificationToken) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'users', - 'emailVerificationToken', - filter.emailVerificationToken, - ), - }; - } - - if (filter.passwordResetToken) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'users', - 'passwordResetToken', - filter.passwordResetToken, - ), - }; - } - - if (filter.provider) { - where = { - ...where, - [Op.and]: Utils.ilike('users', 'provider', filter.provider), - }; - } - - if (filter.emailVerificationTokenExpiresAtRange) { - const [start, end] = filter.emailVerificationTokenExpiresAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - emailVerificationTokenExpiresAt: { - ...where.emailVerificationTokenExpiresAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - emailVerificationTokenExpiresAt: { - ...where.emailVerificationTokenExpiresAt, - [Op.lte]: end, - }, - }; - } - } - - if (filter.passwordResetTokenExpiresAtRange) { - const [start, end] = filter.passwordResetTokenExpiresAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - passwordResetTokenExpiresAt: { - ...where.passwordResetTokenExpiresAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - passwordResetTokenExpiresAt: { - ...where.passwordResetTokenExpiresAt, - [Op.lte]: end, - }, - }; - } - } - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true', - }; - } - - if (filter.disabled) { - where = { - ...where, - disabled: filter.disabled, - }; - } - - if (filter.emailVerified) { - where = { - ...where, - emailVerified: filter.emailVerified, - }; - } - - if (filter.organizations) { - const listItems = filter.organizations.split('|').map((item) => { - return Utils.uuid(item); - }); - - where = { - ...where, - organizationsId: { [Op.or]: listItems }, - }; - } - - if (filter.custom_permissions) { - const searchTerms = filter.custom_permissions.split('|'); - - include = [ - { - model: db.permissions, - as: 'custom_permissions_filter', - required: searchTerms.length > 0, - where: - searchTerms.length > 0 - ? { - [Op.or]: [ - { - id: { - [Op.in]: searchTerms.map((term) => Utils.uuid(term)), - }, - }, - { - name: { - [Op.or]: searchTerms.map((term) => ({ - [Op.iLike]: `%${term}%`, - })), - }, - }, - ], - } - : undefined, - }, - ...include, - ]; - } - - if (filter.createdAtRange) { - const [start, end] = filter.createdAtRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - ['createdAt']: { - ...where.createdAt, - [Op.lte]: end, - }, - }; - } - } - } - - if (globalAccess) { - delete where.organizationsId; - } - - 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.users.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, - globalAccess, - organizationId, - ) { - let where = {}; - - if (!globalAccess && organizationId) { - where.organizationId = organizationId; - } - - if (query) { - where = { - [Op.or]: [ - { ['id']: Utils.uuid(query) }, - Utils.ilike('users', 'firstName', query), - ], - }; - } - - const records = await db.users.findAll({ - attributes: ['id', 'firstName'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['firstName', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.firstName, - })); - } - - static async createFromAuth(data, options) { - const transaction = (options && options.transaction) || undefined; - const users = await db.users.create( - { - email: data.email, - firstName: data.firstName, - authenticationUid: data.authenticationUid, - password: data.password, - - organizationId: data.organizationId, - }, - { transaction }, - ); - - const app_role = await db.roles.findOne({ - where: { name: config.roles?.user || 'User' }, - }); - if (app_role?.id) { - await users.setApp_role(app_role?.id || null, { - transaction, - }); - } - - await users.update( - { - authenticationUid: users.id, - }, - { transaction }, - ); - - delete users.password; - return users; - } - - static async updatePassword(id, password, options) { - const currentUser = (options && options.currentUser) || { id: null }; - - const transaction = (options && options.transaction) || undefined; - - const users = await db.users.findByPk(id, { - transaction, - }); - - await users.update( - { - password, - authenticationUid: id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - return users; - } - - static async generateEmailVerificationToken(email, options) { - return this._generateToken( - ['emailVerificationToken', 'emailVerificationTokenExpiresAt'], - email, - options, - ); - } - - static async generatePasswordResetToken(email, options) { - return this._generateToken( - ['passwordResetToken', 'passwordResetTokenExpiresAt'], - email, - options, - ); - } - - static async findByPasswordResetToken(token, options) { - const transaction = (options && options.transaction) || undefined; - - return db.users.findOne( - { - where: { - passwordResetToken: token, - passwordResetTokenExpiresAt: { - [db.Sequelize.Op.gt]: Date.now(), - }, - }, - }, - { transaction }, - ); - } - - static async findByEmailVerificationToken(token, options) { - const transaction = (options && options.transaction) || undefined; - return db.users.findOne( - { - where: { - emailVerificationToken: token, - emailVerificationTokenExpiresAt: { - [db.Sequelize.Op.gt]: Date.now(), - }, - }, - }, - { transaction }, - ); - } - - static async markEmailVerified(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const users = await db.users.findByPk(id, { - transaction, - }); - - await users.update( - { - emailVerified: true, - updatedById: currentUser.id, - }, - { transaction }, - ); - - return true; - } - - static async _generateToken(keyNames, email, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - const users = await db.users.findOne( - { - where: { email: email.toLowerCase() }, - }, - { - transaction, - }, - ); - - const token = crypto.randomBytes(20).toString('hex'); - const tokenExpiresAt = Date.now() + 360000; - - if (users) { - await users.update( - { - [keyNames[0]]: token, - [keyNames[1]]: tokenExpiresAt, - updatedById: currentUser.id, - }, - { transaction }, - ); - } - - return token; - } -}; diff --git a/backend/src/db/migrations/1748875259190.js b/backend/src/db/migrations/1748875259190.js new file mode 100644 index 0000000..2a63a66 --- /dev/null +++ b/backend/src/db/migrations/1748875259190.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('users', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'users', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1748875298946.js b/backend/src/db/migrations/1748875298946.js new file mode 100644 index 0000000..b808a95 --- /dev/null +++ b/backend/src/db/migrations/1748875298946.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('attendances', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'attendances', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1748875330040.js b/backend/src/db/migrations/1748875330040.js new file mode 100644 index 0000000..f410a13 --- /dev/null +++ b/backend/src/db/migrations/1748875330040.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('payrolls', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'payrolls', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1748875356202.js b/backend/src/db/migrations/1748875356202.js new file mode 100644 index 0000000..0692552 --- /dev/null +++ b/backend/src/db/migrations/1748875356202.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('roles', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'roles', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1748875413338.js b/backend/src/db/migrations/1748875413338.js new file mode 100644 index 0000000..81e3fc5 --- /dev/null +++ b/backend/src/db/migrations/1748875413338.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('employees', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'employees', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1748875435750.js b/backend/src/db/migrations/1748875435750.js new file mode 100644 index 0000000..ffb1e8f --- /dev/null +++ b/backend/src/db/migrations/1748875435750.js @@ -0,0 +1,72 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('permissions', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.createTable( + 'permissions', + { + id: { + type: Sequelize.DataTypes.UUID, + defaultValue: Sequelize.DataTypes.UUIDV4, + primaryKey: true, + }, + createdById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + updatedById: { + type: Sequelize.DataTypes.UUID, + references: { + key: 'id', + model: 'users', + }, + }, + createdAt: { type: Sequelize.DataTypes.DATE }, + updatedAt: { type: Sequelize.DataTypes.DATE }, + deletedAt: { type: Sequelize.DataTypes.DATE }, + importHash: { + type: Sequelize.DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/models/attendances.js b/backend/src/db/models/attendances.js deleted file mode 100644 index f018c6c..0000000 --- a/backend/src/db/models/attendances.js +++ /dev/null @@ -1,87 +0,0 @@ -const config = require('../../config'); -const providers = config.providers; -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -module.exports = function (sequelize, DataTypes) { - const attendances = sequelize.define( - 'attendances', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - time_in: { - type: DataTypes.DATE, - }, - - time_out: { - type: DataTypes.DATE, - }, - - total_hours: { - type: DataTypes.DECIMAL, - }, - - gps_location: { - type: DataTypes.TEXT, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - attendances.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.attendances.belongsTo(db.employees, { - as: 'employee', - foreignKey: { - name: 'employeeId', - }, - constraints: false, - }); - - db.attendances.belongsTo(db.organizations, { - as: 'organizations', - foreignKey: { - name: 'organizationsId', - }, - constraints: false, - }); - - db.attendances.hasMany(db.file, { - as: 'selfie', - foreignKey: 'belongsToId', - constraints: false, - scope: { - belongsTo: db.attendances.getTableName(), - belongsToColumn: 'selfie', - }, - }); - - db.attendances.belongsTo(db.users, { - as: 'createdBy', - }); - - db.attendances.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return attendances; -}; diff --git a/backend/src/db/models/employees.js b/backend/src/db/models/employees.js index 679e7cf..2655ded 100644 --- a/backend/src/db/models/employees.js +++ b/backend/src/db/models/employees.js @@ -195,22 +195,6 @@ module.exports = function (sequelize, DataTypes) { employees.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - db.employees.hasMany(db.attendances, { - as: 'attendances_employee', - foreignKey: { - name: 'employeeId', - }, - constraints: false, - }); - - db.employees.hasMany(db.payrolls, { - as: 'payrolls_employee', - foreignKey: { - name: 'employeeId', - }, - constraints: false, - }); - //end loop db.employees.belongsTo(db.organizations, { diff --git a/backend/src/db/models/organizations.js b/backend/src/db/models/organizations.js index 2723a3b..deab601 100644 --- a/backend/src/db/models/organizations.js +++ b/backend/src/db/models/organizations.js @@ -34,46 +34,6 @@ module.exports = function (sequelize, DataTypes) { organizations.associate = (db) => { /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - db.organizations.hasMany(db.users, { - as: 'users_organizations', - foreignKey: { - name: 'organizationsId', - }, - constraints: false, - }); - - db.organizations.hasMany(db.attendances, { - as: 'attendances_organizations', - foreignKey: { - name: 'organizationsId', - }, - constraints: false, - }); - - db.organizations.hasMany(db.employees, { - as: 'employees_organization', - foreignKey: { - name: 'organizationId', - }, - constraints: false, - }); - - db.organizations.hasMany(db.employees, { - as: 'employees_organizations', - foreignKey: { - name: 'organizationsId', - }, - constraints: false, - }); - - db.organizations.hasMany(db.payrolls, { - as: 'payrolls_organizations', - foreignKey: { - name: 'organizationsId', - }, - constraints: false, - }); - //end loop db.organizations.belongsTo(db.users, { diff --git a/backend/src/db/models/payrolls.js b/backend/src/db/models/payrolls.js deleted file mode 100644 index 513e8d4..0000000 --- a/backend/src/db/models/payrolls.js +++ /dev/null @@ -1,65 +0,0 @@ -const config = require('../../config'); -const providers = config.providers; -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -module.exports = function (sequelize, DataTypes) { - const payrolls = sequelize.define( - 'payrolls', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - total_salary: { - type: DataTypes.DECIMAL, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - payrolls.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.payrolls.belongsTo(db.employees, { - as: 'employee', - foreignKey: { - name: 'employeeId', - }, - constraints: false, - }); - - db.payrolls.belongsTo(db.organizations, { - as: 'organizations', - foreignKey: { - name: 'organizationsId', - }, - constraints: false, - }); - - db.payrolls.belongsTo(db.users, { - as: 'createdBy', - }); - - db.payrolls.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return payrolls; -}; diff --git a/backend/src/db/models/permissions.js b/backend/src/db/models/permissions.js deleted file mode 100644 index d647c73..0000000 --- a/backend/src/db/models/permissions.js +++ /dev/null @@ -1,49 +0,0 @@ -const config = require('../../config'); -const providers = config.providers; -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -module.exports = function (sequelize, DataTypes) { - const permissions = sequelize.define( - 'permissions', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - name: { - type: DataTypes.TEXT, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - permissions.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.permissions.belongsTo(db.users, { - as: 'createdBy', - }); - - db.permissions.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return permissions; -}; diff --git a/backend/src/db/models/roles.js b/backend/src/db/models/roles.js deleted file mode 100644 index 0f144d5..0000000 --- a/backend/src/db/models/roles.js +++ /dev/null @@ -1,86 +0,0 @@ -const config = require('../../config'); -const providers = config.providers; -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -module.exports = function (sequelize, DataTypes) { - const roles = sequelize.define( - 'roles', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - name: { - type: DataTypes.TEXT, - }, - - role_customization: { - type: DataTypes.TEXT, - }, - - globalAccess: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - roles.associate = (db) => { - db.roles.belongsToMany(db.permissions, { - as: 'permissions', - foreignKey: { - name: 'roles_permissionsId', - }, - constraints: false, - through: 'rolesPermissionsPermissions', - }); - - db.roles.belongsToMany(db.permissions, { - as: 'permissions_filter', - foreignKey: { - name: 'roles_permissionsId', - }, - constraints: false, - through: 'rolesPermissionsPermissions', - }); - - /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - db.roles.hasMany(db.users, { - as: 'users_app_role', - foreignKey: { - name: 'app_roleId', - }, - constraints: false, - }); - - //end loop - - db.roles.belongsTo(db.users, { - as: 'createdBy', - }); - - db.roles.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return roles; -}; diff --git a/backend/src/db/models/users.js b/backend/src/db/models/users.js deleted file mode 100644 index 8c52d13..0000000 --- a/backend/src/db/models/users.js +++ /dev/null @@ -1,179 +0,0 @@ -const config = require('../../config'); -const providers = config.providers; -const crypto = require('crypto'); -const bcrypt = require('bcrypt'); -const moment = require('moment'); - -module.exports = function (sequelize, DataTypes) { - const users = sequelize.define( - 'users', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - firstName: { - type: DataTypes.TEXT, - }, - - lastName: { - type: DataTypes.TEXT, - }, - - phoneNumber: { - type: DataTypes.TEXT, - }, - - email: { - type: DataTypes.TEXT, - }, - - disabled: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - }, - - password: { - type: DataTypes.TEXT, - }, - - emailVerified: { - type: DataTypes.BOOLEAN, - - allowNull: false, - defaultValue: false, - }, - - emailVerificationToken: { - type: DataTypes.TEXT, - }, - - emailVerificationTokenExpiresAt: { - type: DataTypes.DATE, - }, - - passwordResetToken: { - type: DataTypes.TEXT, - }, - - passwordResetTokenExpiresAt: { - type: DataTypes.DATE, - }, - - provider: { - type: DataTypes.TEXT, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - users.associate = (db) => { - db.users.belongsToMany(db.permissions, { - as: 'custom_permissions', - foreignKey: { - name: 'users_custom_permissionsId', - }, - constraints: false, - through: 'usersCustom_permissionsPermissions', - }); - - db.users.belongsToMany(db.permissions, { - as: 'custom_permissions_filter', - foreignKey: { - name: 'users_custom_permissionsId', - }, - constraints: false, - through: 'usersCustom_permissionsPermissions', - }); - - /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity - - //end loop - - db.users.belongsTo(db.roles, { - as: 'app_role', - foreignKey: { - name: 'app_roleId', - }, - constraints: false, - }); - - db.users.belongsTo(db.organizations, { - as: 'organizations', - foreignKey: { - name: 'organizationsId', - }, - constraints: false, - }); - - db.users.hasMany(db.file, { - as: 'avatar', - foreignKey: 'belongsToId', - constraints: false, - scope: { - belongsTo: db.users.getTableName(), - belongsToColumn: 'avatar', - }, - }); - - db.users.belongsTo(db.users, { - as: 'createdBy', - }); - - db.users.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - users.beforeCreate((users, options) => { - users = trimStringFields(users); - - if ( - users.provider !== providers.LOCAL && - Object.values(providers).indexOf(users.provider) > -1 - ) { - users.emailVerified = true; - - if (!users.password) { - const password = crypto.randomBytes(20).toString('hex'); - - const hashedPassword = bcrypt.hashSync( - password, - config.bcrypt.saltRounds, - ); - - users.password = hashedPassword; - } - } - }); - - users.beforeUpdate((users, options) => { - users = trimStringFields(users); - }); - - return users; -}; - -function trimStringFields(users) { - users.email = users.email.trim(); - - users.firstName = users.firstName ? users.firstName.trim() : null; - - users.lastName = users.lastName ? users.lastName.trim() : null; - - return users; -} diff --git a/backend/src/db/seeders/20200430130759-admin-user.js b/backend/src/db/seeders/20200430130759-admin-user.js index b60808f..e69de29 100644 --- a/backend/src/db/seeders/20200430130759-admin-user.js +++ b/backend/src/db/seeders/20200430130759-admin-user.js @@ -1,84 +0,0 @@ -'use strict'; -const bcrypt = require('bcrypt'); -const config = require('../../config'); - -const ids = [ - '193bf4b5-9f07-4bd5-9a43-e7e41f3e96af', - 'af5a87be-8f9c-4630-902a-37a60b7005ba', - '5bc531ab-611f-41f3-9373-b7cc5d09c93d', - 'ab4cf9bf-4eef-4107-b73d-9d0274cf69bc', -]; - -module.exports = { - up: async (queryInterface, Sequelize) => { - let admin_hash = bcrypt.hashSync( - config.admin_pass, - config.bcrypt.saltRounds, - ); - let user_hash = bcrypt.hashSync(config.user_pass, config.bcrypt.saltRounds); - - try { - await queryInterface.bulkInsert('users', [ - { - id: ids[0], - firstName: 'Admin', - email: config.admin_email, - emailVerified: true, - provider: config.providers.LOCAL, - password: admin_hash, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: ids[1], - firstName: 'John', - email: 'john@doe.com', - emailVerified: true, - provider: config.providers.LOCAL, - password: user_hash, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: ids[2], - firstName: 'Client', - email: 'client@hello.com', - emailVerified: true, - provider: config.providers.LOCAL, - password: user_hash, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - id: ids[3], - firstName: 'Super Admin', - email: 'super_admin@flatlogic.com', - emailVerified: true, - provider: config.providers.LOCAL, - password: admin_hash, - createdAt: new Date(), - updatedAt: new Date(), - }, - ]); - } catch (error) { - console.error('Error during bulkInsert:', error); - throw error; - } - }, - down: async (queryInterface, Sequelize) => { - try { - await queryInterface.bulkDelete( - 'users', - { - id: { - [Sequelize.Op.in]: ids, - }, - }, - {}, - ); - } catch (error) { - console.error('Error during bulkDelete:', error); - throw error; - } - }, -}; diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index cc3200d..e69de29 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -1,793 +0,0 @@ -const { v4: uuid } = require('uuid'); - -module.exports = { - /** - * @param{import("sequelize").QueryInterface} queryInterface - * @return {Promise} - */ - async up(queryInterface) { - const createdAt = new Date(); - const updatedAt = new Date(); - - /** @type {Map} */ - const idMap = new Map(); - - /** - * @param {string} key - * @return {string} - */ - function getId(key) { - if (idMap.has(key)) { - return idMap.get(key); - } - const id = uuid(); - idMap.set(key, id); - return id; - } - - await queryInterface.bulkInsert('roles', [ - { - id: getId('SuperAdmin'), - name: 'Super Administrator', - createdAt, - updatedAt, - }, - - { - id: getId('Administrator'), - name: 'Administrator', - createdAt, - updatedAt, - }, - - { - id: getId('ChiefOperationsOfficer'), - name: 'Chief Operations Officer', - createdAt, - updatedAt, - }, - - { - id: getId('HumanResourcesDirector'), - name: 'Human Resources Director', - createdAt, - updatedAt, - }, - - { - id: getId('FinanceManager'), - name: 'Finance Manager', - createdAt, - updatedAt, - }, - - { - id: getId('ITSupervisor'), - name: 'IT Supervisor', - createdAt, - updatedAt, - }, - - { - id: getId('SalesCoordinator'), - name: 'Sales Coordinator', - createdAt, - updatedAt, - }, - - { id: getId('Public'), name: 'Public', createdAt, updatedAt }, - ]); - - /** - * @param {string} name - */ - function createPermissions(name) { - return [ - { - id: getId(`CREATE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `CREATE_${name.toUpperCase()}`, - }, - { - id: getId(`READ_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `READ_${name.toUpperCase()}`, - }, - { - id: getId(`UPDATE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `UPDATE_${name.toUpperCase()}`, - }, - { - id: getId(`DELETE_${name.toUpperCase()}`), - createdAt, - updatedAt, - name: `DELETE_${name.toUpperCase()}`, - }, - ]; - } - - const entities = [ - 'users', - 'attendances', - 'employees', - 'payrolls', - 'roles', - 'permissions', - 'organizations', - , - ]; - await queryInterface.bulkInsert( - 'permissions', - entities.flatMap(createPermissions), - ); - await queryInterface.bulkInsert('permissions', [ - { - id: getId(`READ_API_DOCS`), - createdAt, - updatedAt, - name: `READ_API_DOCS`, - }, - ]); - await queryInterface.bulkInsert('permissions', [ - { - id: getId(`CREATE_SEARCH`), - createdAt, - updatedAt, - name: `CREATE_SEARCH`, - }, - ]); - - await queryInterface.bulkUpdate( - 'roles', - { globalAccess: true }, - { id: getId('SuperAdmin') }, - ); - - await queryInterface.sequelize - .query(`create table "rolesPermissionsPermissions" -( -"createdAt" timestamp with time zone not null, -"updatedAt" timestamp with time zone not null, -"roles_permissionsId" uuid not null, -"permissionId" uuid not null, -primary key ("roles_permissionsId", "permissionId") -);`); - - await queryInterface.bulkInsert('rolesPermissionsPermissions', [ - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('CREATE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('READ_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('UPDATE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('DELETE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('CREATE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('READ_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('UPDATE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('CREATE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('READ_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ITSupervisor'), - permissionId: getId('CREATE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ITSupervisor'), - permissionId: getId('READ_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SalesCoordinator'), - permissionId: getId('CREATE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SalesCoordinator'), - permissionId: getId('READ_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('CREATE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('READ_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('UPDATE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('DELETE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('CREATE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('READ_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('UPDATE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('CREATE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('READ_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ITSupervisor'), - permissionId: getId('CREATE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ITSupervisor'), - permissionId: getId('READ_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SalesCoordinator'), - permissionId: getId('CREATE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SalesCoordinator'), - permissionId: getId('READ_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('CREATE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('READ_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('UPDATE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('DELETE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('CREATE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('READ_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('CREATE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('READ_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('UPDATE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('DELETE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ITSupervisor'), - permissionId: getId('CREATE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ITSupervisor'), - permissionId: getId('READ_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SalesCoordinator'), - permissionId: getId('CREATE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ChiefOperationsOfficer'), - permissionId: getId('CREATE_SEARCH'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('HumanResourcesDirector'), - permissionId: getId('CREATE_SEARCH'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('FinanceManager'), - permissionId: getId('CREATE_SEARCH'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('ITSupervisor'), - permissionId: getId('CREATE_SEARCH'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SalesCoordinator'), - permissionId: getId('CREATE_SEARCH'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_USERS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_USERS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_USERS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_USERS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_ATTENDANCES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_ATTENDANCES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_ATTENDANCES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_EMPLOYEES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_EMPLOYEES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_EMPLOYEES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_PAYROLLS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_PAYROLLS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_PAYROLLS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_USERS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_USERS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('UPDATE_USERS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('DELETE_USERS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_ATTENDANCES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_ATTENDANCES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('UPDATE_ATTENDANCES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('DELETE_ATTENDANCES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_EMPLOYEES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_EMPLOYEES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('UPDATE_EMPLOYEES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('DELETE_EMPLOYEES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_PAYROLLS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_PAYROLLS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('UPDATE_PAYROLLS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('DELETE_PAYROLLS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_ROLES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_ROLES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('UPDATE_ROLES'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('DELETE_ROLES'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_PERMISSIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_PERMISSIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('UPDATE_PERMISSIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('DELETE_PERMISSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_ORGANIZATIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_ORGANIZATIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('UPDATE_ORGANIZATIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('DELETE_ORGANIZATIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('READ_API_DOCS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('SuperAdmin'), - permissionId: getId('CREATE_SEARCH'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_API_DOCS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_SEARCH'), - }, - ]); - - await queryInterface.sequelize.query( - `UPDATE "users" SET "app_roleId"='${getId( - 'SuperAdmin', - )}' WHERE "email"='super_admin@flatlogic.com'`, - ); - await queryInterface.sequelize.query( - `UPDATE "users" SET "app_roleId"='${getId( - 'Administrator', - )}' WHERE "email"='admin@flatlogic.com'`, - ); - - await queryInterface.sequelize.query( - `UPDATE "users" SET "app_roleId"='${getId( - 'ChiefOperationsOfficer', - )}' WHERE "email"='client@hello.com'`, - ); - await queryInterface.sequelize.query( - `UPDATE "users" SET "app_roleId"='${getId( - 'HumanResourcesDirector', - )}' WHERE "email"='john@doe.com'`, - ); - }, -}; diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 0fd6604..e69de29 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -1,623 +0,0 @@ -const db = require('../models'); -const Users = db.users; - -const Attendances = db.attendances; - -const Employees = db.employees; - -const Payrolls = db.payrolls; - -const Organizations = db.organizations; - -const AttendancesData = [ - { - time_in: new Date('2023-10-01T09:00:00Z'), - - time_out: new Date('2023-10-01T17:00:00Z'), - - total_hours: 8, - - gps_location: 'Springfield, 12345', - - // type code here for "images" field - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, - - { - time_in: new Date('2023-10-01T09:15:00Z'), - - time_out: new Date('2023-10-01T17:15:00Z'), - - total_hours: 8, - - gps_location: 'Springfield, 12345', - - // type code here for "images" field - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, - - { - time_in: new Date('2023-10-01T09:30:00Z'), - - time_out: new Date('2023-10-01T17:30:00Z'), - - total_hours: 8, - - gps_location: 'Springfield, 12345', - - // type code here for "images" field - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, -]; - -const EmployeesData = [ - { - title: 'Mr', - - first_name: 'John', - - middle_name: 'A', - - last_name: 'Doe', - - date_of_birth: new Date('1985-06-15T00:00:00Z'), - - sex: 'F', - - aadhar_number: '123456789012', - - pan_number: 'ABCDE1234F', - - // type code here for "images" field - - cell_phone_number: '9876543210', - - current_residential_address: '123 Main St, Springfield', - - permanent_residential_address: '456 Elm St, Springfield', - - designation: 'Software Engineer', - - department: 'IT', - - reporting_manager: 'Jane Smith', - - flexible_working_hours: true, - - office_start_time: new Date('2023-10-01T09:00:00Z'), - - office_end_time: new Date('2023-10-01T17:00:00Z'), - - basic_salary: 50000, - - hra: 10000, - - allowance_1: 5000, - - allowance_2: 3000, - - allowance_3: 2000, - - advance_paid_1: 0, - - advance_paid_2: 0, - - employer_pf_contribution: 1800, - - employee_pf_contribution: 1800, - - professional_tax: 200, - - advances_1: 0, - - advances_2: 0, - - deduction_1: 0, - - deduction_2: 0, - - allowed_leaves: 24, - - leaves_applicable_from: new Date('2023-01-01T00:00:00Z'), - - weekly_off: 'Thursday', - - employee_type: 'Roaming', - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, - - { - title: 'Mr', - - first_name: 'Emily', - - middle_name: 'B', - - last_name: 'Clark', - - date_of_birth: new Date('1990-08-22T00:00:00Z'), - - sex: 'F', - - aadhar_number: '234567890123', - - pan_number: 'BCDEF2345G', - - // type code here for "images" field - - cell_phone_number: '9876543211', - - current_residential_address: '789 Oak St, Springfield', - - permanent_residential_address: '101 Pine St, Springfield', - - designation: 'HR Manager', - - department: 'HR', - - reporting_manager: 'Michael Brown', - - flexible_working_hours: true, - - office_start_time: new Date('2023-10-01T09:00:00Z'), - - office_end_time: new Date('2023-10-01T17:00:00Z'), - - basic_salary: 60000, - - hra: 12000, - - allowance_1: 6000, - - allowance_2: 4000, - - allowance_3: 3000, - - advance_paid_1: 0, - - advance_paid_2: 0, - - employer_pf_contribution: 2160, - - employee_pf_contribution: 2160, - - professional_tax: 250, - - advances_1: 0, - - advances_2: 0, - - deduction_1: 0, - - deduction_2: 0, - - allowed_leaves: 24, - - leaves_applicable_from: new Date('2023-01-01T00:00:00Z'), - - weekly_off: 'Saturday', - - employee_type: 'Stationed', - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, - - { - title: 'Mr', - - first_name: 'David', - - middle_name: 'C', - - last_name: 'Johnson', - - date_of_birth: new Date('1982-11-30T00:00:00Z'), - - sex: 'M', - - aadhar_number: '345678901234', - - pan_number: 'CDEFG3456H', - - // type code here for "images" field - - cell_phone_number: '9876543212', - - current_residential_address: '202 Birch St, Springfield', - - permanent_residential_address: '303 Cedar St, Springfield', - - designation: 'Sales Executive', - - department: 'Sales', - - reporting_manager: 'Sarah Lee', - - flexible_working_hours: true, - - office_start_time: new Date('2023-10-01T09:00:00Z'), - - office_end_time: new Date('2023-10-01T17:00:00Z'), - - basic_salary: 45000, - - hra: 9000, - - allowance_1: 4500, - - allowance_2: 2500, - - allowance_3: 1500, - - advance_paid_1: 0, - - advance_paid_2: 0, - - employer_pf_contribution: 1620, - - employee_pf_contribution: 1620, - - professional_tax: 180, - - advances_1: 0, - - advances_2: 0, - - deduction_1: 0, - - deduction_2: 0, - - allowed_leaves: 24, - - leaves_applicable_from: new Date('2023-01-01T00:00:00Z'), - - weekly_off: 'Thursday', - - employee_type: 'Roaming', - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, -]; - -const PayrollsData = [ - { - total_salary: 75000, - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, - - { - total_salary: 90000, - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, - - { - total_salary: 68000, - - // type code here for "relation_one" field - - // type code here for "relation_one" field - }, -]; - -const OrganizationsData = [ - { - name: 'Tech Solutions Inc', - }, - - { - name: 'Creative Minds Ltd', - }, - - { - name: 'Finance Experts Co', - }, -]; - -// Similar logic for "relation_many" - -async function associateUserWithOrganization() { - const relatedOrganization0 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const User0 = await Users.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (User0?.setOrganization) { - await User0.setOrganization(relatedOrganization0); - } - - const relatedOrganization1 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const User1 = await Users.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (User1?.setOrganization) { - await User1.setOrganization(relatedOrganization1); - } - - const relatedOrganization2 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const User2 = await Users.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (User2?.setOrganization) { - await User2.setOrganization(relatedOrganization2); - } -} - -async function associateAttendanceWithEmployee() { - const relatedEmployee0 = await Employees.findOne({ - offset: Math.floor(Math.random() * (await Employees.count())), - }); - const Attendance0 = await Attendances.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Attendance0?.setEmployee) { - await Attendance0.setEmployee(relatedEmployee0); - } - - const relatedEmployee1 = await Employees.findOne({ - offset: Math.floor(Math.random() * (await Employees.count())), - }); - const Attendance1 = await Attendances.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Attendance1?.setEmployee) { - await Attendance1.setEmployee(relatedEmployee1); - } - - const relatedEmployee2 = await Employees.findOne({ - offset: Math.floor(Math.random() * (await Employees.count())), - }); - const Attendance2 = await Attendances.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Attendance2?.setEmployee) { - await Attendance2.setEmployee(relatedEmployee2); - } -} - -async function associateAttendanceWithOrganization() { - const relatedOrganization0 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Attendance0 = await Attendances.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Attendance0?.setOrganization) { - await Attendance0.setOrganization(relatedOrganization0); - } - - const relatedOrganization1 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Attendance1 = await Attendances.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Attendance1?.setOrganization) { - await Attendance1.setOrganization(relatedOrganization1); - } - - const relatedOrganization2 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Attendance2 = await Attendances.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Attendance2?.setOrganization) { - await Attendance2.setOrganization(relatedOrganization2); - } -} - -async function associateEmployeeWithOrganization() { - const relatedOrganization0 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Employee0 = await Employees.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Employee0?.setOrganization) { - await Employee0.setOrganization(relatedOrganization0); - } - - const relatedOrganization1 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Employee1 = await Employees.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Employee1?.setOrganization) { - await Employee1.setOrganization(relatedOrganization1); - } - - const relatedOrganization2 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Employee2 = await Employees.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Employee2?.setOrganization) { - await Employee2.setOrganization(relatedOrganization2); - } -} - -async function associateEmployeeWithOrganization() { - const relatedOrganization0 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Employee0 = await Employees.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Employee0?.setOrganization) { - await Employee0.setOrganization(relatedOrganization0); - } - - const relatedOrganization1 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Employee1 = await Employees.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Employee1?.setOrganization) { - await Employee1.setOrganization(relatedOrganization1); - } - - const relatedOrganization2 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Employee2 = await Employees.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Employee2?.setOrganization) { - await Employee2.setOrganization(relatedOrganization2); - } -} - -async function associatePayrollWithEmployee() { - const relatedEmployee0 = await Employees.findOne({ - offset: Math.floor(Math.random() * (await Employees.count())), - }); - const Payroll0 = await Payrolls.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Payroll0?.setEmployee) { - await Payroll0.setEmployee(relatedEmployee0); - } - - const relatedEmployee1 = await Employees.findOne({ - offset: Math.floor(Math.random() * (await Employees.count())), - }); - const Payroll1 = await Payrolls.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Payroll1?.setEmployee) { - await Payroll1.setEmployee(relatedEmployee1); - } - - const relatedEmployee2 = await Employees.findOne({ - offset: Math.floor(Math.random() * (await Employees.count())), - }); - const Payroll2 = await Payrolls.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Payroll2?.setEmployee) { - await Payroll2.setEmployee(relatedEmployee2); - } -} - -async function associatePayrollWithOrganization() { - const relatedOrganization0 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Payroll0 = await Payrolls.findOne({ - order: [['id', 'ASC']], - offset: 0, - }); - if (Payroll0?.setOrganization) { - await Payroll0.setOrganization(relatedOrganization0); - } - - const relatedOrganization1 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Payroll1 = await Payrolls.findOne({ - order: [['id', 'ASC']], - offset: 1, - }); - if (Payroll1?.setOrganization) { - await Payroll1.setOrganization(relatedOrganization1); - } - - const relatedOrganization2 = await Organizations.findOne({ - offset: Math.floor(Math.random() * (await Organizations.count())), - }); - const Payroll2 = await Payrolls.findOne({ - order: [['id', 'ASC']], - offset: 2, - }); - if (Payroll2?.setOrganization) { - await Payroll2.setOrganization(relatedOrganization2); - } -} - -module.exports = { - up: async (queryInterface, Sequelize) => { - await Attendances.bulkCreate(AttendancesData); - - await Employees.bulkCreate(EmployeesData); - - await Payrolls.bulkCreate(PayrollsData); - - await Organizations.bulkCreate(OrganizationsData); - - await Promise.all([ - // Similar logic for "relation_many" - - await associateUserWithOrganization(), - - await associateAttendanceWithEmployee(), - - await associateAttendanceWithOrganization(), - - await associateEmployeeWithOrganization(), - - await associateEmployeeWithOrganization(), - - await associatePayrollWithEmployee(), - - await associatePayrollWithOrganization(), - ]); - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete('attendances', null, {}); - - await queryInterface.bulkDelete('employees', null, {}); - - await queryInterface.bulkDelete('payrolls', null, {}); - - await queryInterface.bulkDelete('organizations', null, {}); - }, -}; diff --git a/backend/src/index.js b/backend/src/index.js index f19c182..74d7256 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -21,18 +21,6 @@ const openaiRoutes = require('./routes/openai'); const contactFormRoutes = require('./routes/contactForm'); -const usersRoutes = require('./routes/users'); - -const attendancesRoutes = require('./routes/attendances'); - -const employeesRoutes = require('./routes/employees'); - -const payrollsRoutes = require('./routes/payrolls'); - -const rolesRoutes = require('./routes/roles'); - -const permissionsRoutes = require('./routes/permissions'); - const organizationsRoutes = require('./routes/organizations'); const getBaseUrl = (url) => { @@ -100,42 +88,6 @@ app.use('/api/file', fileRoutes); app.use('/api/pexels', pexelsRoutes); app.enable('trust proxy'); -app.use( - '/api/users', - passport.authenticate('jwt', { session: false }), - usersRoutes, -); - -app.use( - '/api/attendances', - passport.authenticate('jwt', { session: false }), - attendancesRoutes, -); - -app.use( - '/api/employees', - passport.authenticate('jwt', { session: false }), - employeesRoutes, -); - -app.use( - '/api/payrolls', - passport.authenticate('jwt', { session: false }), - payrollsRoutes, -); - -app.use( - '/api/roles', - passport.authenticate('jwt', { session: false }), - rolesRoutes, -); - -app.use( - '/api/permissions', - passport.authenticate('jwt', { session: false }), - permissionsRoutes, -); - app.use( '/api/organizations', passport.authenticate('jwt', { session: false }), diff --git a/backend/src/routes/attendances.js b/backend/src/routes/attendances.js deleted file mode 100644 index 3ac8b44..0000000 --- a/backend/src/routes/attendances.js +++ /dev/null @@ -1,467 +0,0 @@ -const express = require('express'); - -const AttendancesService = require('../services/attendances'); -const AttendancesDBApi = require('../db/api/attendances'); -const wrapAsync = require('../helpers').wrapAsync; - -const config = require('../config'); - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('attendances')); - -/** - * @swagger - * components: - * schemas: - * Attendances: - * type: object - * properties: - - * gps_location: - * type: string - * default: gps_location - - * total_hours: - * type: integer - * format: int64 - - */ - -/** - * @swagger - * tags: - * name: Attendances - * description: The Attendances managing API - */ - -/** - * @swagger - * /api/attendances: - * post: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Add new item - * description: Add new item - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Attendances" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Attendances" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - */ -router.post( - '/', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await AttendancesService.create( - req.body.data, - req.currentUser, - true, - link.host, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Attendances" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Attendances" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await AttendancesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/attendances/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Attendances" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Attendances" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put( - '/:id', - wrapAsync(async (req, res) => { - await AttendancesService.update( - req.body.data, - req.body.id, - req.currentUser, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/attendances/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Attendances" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await AttendancesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/attendances/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Attendances" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await AttendancesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/attendances: - * get: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Get all attendances - * description: Get all attendances - * responses: - * 200: - * description: Attendances list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Attendances" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await AttendancesDBApi.findAll(req.query, globalAccess, { - currentUser, - }); - if (filetype && filetype === 'csv') { - const fields = [ - 'id', - 'gps_location', - - 'total_hours', - 'time_in', - 'time_out', - ]; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - }), -); - -/** - * @swagger - * /api/attendances/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Count all attendances - * description: Count all attendances - * responses: - * 200: - * description: Attendances count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Attendances" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/count', - wrapAsync(async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await AttendancesDBApi.findAll(req.query, globalAccess, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/attendances/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Find all attendances that match search criteria - * description: Find all attendances that match search criteria - * responses: - * 200: - * description: Attendances list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Attendances" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const organizationId = req.currentUser.organization?.id; - - const payload = await AttendancesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - globalAccess, - organizationId, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/attendances/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Attendances] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Attendances" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await AttendancesDBApi.findBy({ id: req.params.id }); - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/payrolls.js b/backend/src/routes/payrolls.js deleted file mode 100644 index f1299c3..0000000 --- a/backend/src/routes/payrolls.js +++ /dev/null @@ -1,452 +0,0 @@ -const express = require('express'); - -const PayrollsService = require('../services/payrolls'); -const PayrollsDBApi = require('../db/api/payrolls'); -const wrapAsync = require('../helpers').wrapAsync; - -const config = require('../config'); - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('payrolls')); - -/** - * @swagger - * components: - * schemas: - * Payrolls: - * type: object - * properties: - - * total_salary: - * type: integer - * format: int64 - - */ - -/** - * @swagger - * tags: - * name: Payrolls - * description: The Payrolls managing API - */ - -/** - * @swagger - * /api/payrolls: - * post: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Add new item - * description: Add new item - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Payrolls" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Payrolls" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - */ -router.post( - '/', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await PayrollsService.create( - req.body.data, - req.currentUser, - true, - link.host, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Payrolls" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Payrolls" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await PayrollsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/payrolls/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Payrolls" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Payrolls" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put( - '/:id', - wrapAsync(async (req, res) => { - await PayrollsService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/payrolls/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Payrolls" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await PayrollsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/payrolls/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Payrolls" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await PayrollsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/payrolls: - * get: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Get all payrolls - * description: Get all payrolls - * responses: - * 200: - * description: Payrolls list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Payrolls" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await PayrollsDBApi.findAll(req.query, globalAccess, { - currentUser, - }); - if (filetype && filetype === 'csv') { - const fields = ['id', 'total_salary']; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - }), -); - -/** - * @swagger - * /api/payrolls/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Count all payrolls - * description: Count all payrolls - * responses: - * 200: - * description: Payrolls count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Payrolls" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/count', - wrapAsync(async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await PayrollsDBApi.findAll(req.query, globalAccess, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/payrolls/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Find all payrolls that match search criteria - * description: Find all payrolls that match search criteria - * responses: - * 200: - * description: Payrolls list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Payrolls" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const organizationId = req.currentUser.organization?.id; - - const payload = await PayrollsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - globalAccess, - organizationId, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/payrolls/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Payrolls] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Payrolls" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await PayrollsDBApi.findBy({ id: req.params.id }); - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/permissions.js b/backend/src/routes/permissions.js deleted file mode 100644 index c3f1685..0000000 --- a/backend/src/routes/permissions.js +++ /dev/null @@ -1,442 +0,0 @@ -const express = require('express'); - -const PermissionsService = require('../services/permissions'); -const PermissionsDBApi = require('../db/api/permissions'); -const wrapAsync = require('../helpers').wrapAsync; - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('permissions')); - -/** - * @swagger - * components: - * schemas: - * Permissions: - * type: object - * properties: - - * name: - * type: string - * default: name - - */ - -/** - * @swagger - * tags: - * name: Permissions - * description: The Permissions managing API - */ - -/** - * @swagger - * /api/permissions: - * post: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Add new item - * description: Add new item - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Permissions" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - */ -router.post( - '/', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await PermissionsService.create( - req.body.data, - req.currentUser, - true, - link.host, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Permissions" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await PermissionsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/permissions/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Permissions" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put( - '/:id', - wrapAsync(async (req, res) => { - await PermissionsService.update( - req.body.data, - req.body.id, - req.currentUser, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/permissions/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await PermissionsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/permissions/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await PermissionsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/permissions: - * get: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Get all permissions - * description: Get all permissions - * responses: - * 200: - * description: Permissions list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const currentUser = req.currentUser; - const payload = await PermissionsDBApi.findAll(req.query, { currentUser }); - if (filetype && filetype === 'csv') { - const fields = ['id', 'name']; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - }), -); - -/** - * @swagger - * /api/permissions/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Count all permissions - * description: Count all permissions - * responses: - * 200: - * description: Permissions count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/count', - wrapAsync(async (req, res) => { - const currentUser = req.currentUser; - const payload = await PermissionsDBApi.findAll(req.query, null, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/permissions/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Find all permissions that match search criteria - * description: Find all permissions that match search criteria - * responses: - * 200: - * description: Permissions list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Permissions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const payload = await PermissionsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/permissions/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Permissions] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Permissions" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await PermissionsDBApi.findBy({ id: req.params.id }); - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/roles.js b/backend/src/routes/roles.js deleted file mode 100644 index b3999e9..0000000 --- a/backend/src/routes/roles.js +++ /dev/null @@ -1,444 +0,0 @@ -const express = require('express'); - -const RolesService = require('../services/roles'); -const RolesDBApi = require('../db/api/roles'); -const wrapAsync = require('../helpers').wrapAsync; - -const config = require('../config'); - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('roles')); - -/** - * @swagger - * components: - * schemas: - * Roles: - * type: object - * properties: - - * name: - * type: string - * default: name - - */ - -/** - * @swagger - * tags: - * name: Roles - * description: The Roles managing API - */ - -/** - * @swagger - * /api/roles: - * post: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Add new item - * description: Add new item - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Roles" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - */ -router.post( - '/', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await RolesService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Roles" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await RolesService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/roles/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Roles" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put( - '/:id', - wrapAsync(async (req, res) => { - await RolesService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/roles/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await RolesService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/roles/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await RolesService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/roles: - * get: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Get all roles - * description: Get all roles - * responses: - * 200: - * description: Roles list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await RolesDBApi.findAll(req.query, globalAccess, { - currentUser, - }); - if (filetype && filetype === 'csv') { - const fields = ['id', 'name']; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - }), -); - -/** - * @swagger - * /api/roles/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Count all roles - * description: Count all roles - * responses: - * 200: - * description: Roles count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/count', - wrapAsync(async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await RolesDBApi.findAll(req.query, globalAccess, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/roles/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Find all roles that match search criteria - * description: Find all roles that match search criteria - * responses: - * 200: - * description: Roles list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Roles" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const payload = await RolesDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - globalAccess, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/roles/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Roles] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Roles" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await RolesDBApi.findBy({ id: req.params.id }); - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/routes/users.js b/backend/src/routes/users.js deleted file mode 100644 index fa9083e..0000000 --- a/backend/src/routes/users.js +++ /dev/null @@ -1,458 +0,0 @@ -const express = require('express'); - -const UsersService = require('../services/users'); -const UsersDBApi = require('../db/api/users'); -const wrapAsync = require('../helpers').wrapAsync; - -const config = require('../config'); - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('users')); - -/** - * @swagger - * components: - * schemas: - * Users: - * type: object - * properties: - - * firstName: - * type: string - * default: firstName - * lastName: - * type: string - * default: lastName - * phoneNumber: - * type: string - * default: phoneNumber - * email: - * type: string - * default: email - - */ - -/** - * @swagger - * tags: - * name: Users - * description: The Users managing API - */ - -/** - * @swagger - * /api/users: - * post: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Add new item - * description: Add new item - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Users" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Users" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - */ -router.post( - '/', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await UsersService.create(req.body.data, req.currentUser, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/budgets/bulk-import: - * post: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Bulk import items - * description: Bulk import items - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * data: - * description: Data of the updated items - * type: array - * items: - * $ref: "#/components/schemas/Users" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Users" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 405: - * description: Invalid input data - * 500: - * description: Some server error - * - */ -router.post( - '/bulk-import', - wrapAsync(async (req, res) => { - const referer = - req.headers.referer || - `${req.protocol}://${req.hostname}${req.originalUrl}`; - const link = new URL(referer); - await UsersService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/users/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Update the data of the selected item - * description: Update the data of the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to update - * required: true - * schema: - * type: string - * requestBody: - * description: Set new item data - * required: true - * content: - * application/json: - * schema: - * properties: - * id: - * description: ID of the updated item - * type: string - * data: - * description: Data of the updated item - * type: object - * $ref: "#/components/schemas/Users" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Users" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.put( - '/:id', - wrapAsync(async (req, res) => { - await UsersService.update(req.body.data, req.body.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/users/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Delete the selected item - * description: Delete the selected item - * parameters: - * - in: path - * name: id - * description: Item ID to delete - * required: true - * schema: - * type: string - * responses: - * 200: - * description: The item was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Users" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.delete( - '/:id', - wrapAsync(async (req, res) => { - await UsersService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/users/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Delete the selected item list - * description: Delete the selected item list - * requestBody: - * required: true - * content: - * application/json: - * schema: - * properties: - * ids: - * description: IDs of the updated items - * type: array - * responses: - * 200: - * description: The items was successfully deleted - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Users" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await UsersService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/users: - * get: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Get all users - * description: Get all users - * responses: - * 200: - * description: Users list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Users" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/', - wrapAsync(async (req, res) => { - const filetype = req.query.filetype; - - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await UsersDBApi.findAll(req.query, globalAccess, { - currentUser, - }); - if (filetype && filetype === 'csv') { - const fields = ['id', 'firstName', 'lastName', 'phoneNumber', 'email']; - const opts = { fields }; - try { - const csv = parse(payload.rows, opts); - res.status(200).attachment(csv); - res.send(csv); - } catch (err) { - console.error(err); - } - } else { - res.status(200).send(payload); - } - }), -); - -/** - * @swagger - * /api/users/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Count all users - * description: Count all users - * responses: - * 200: - * description: Users count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Users" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get( - '/count', - wrapAsync(async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const currentUser = req.currentUser; - const payload = await UsersDBApi.findAll(req.query, globalAccess, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/users/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Find all users that match search criteria - * description: Find all users that match search criteria - * responses: - * 200: - * description: Users list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Users" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const globalAccess = req.currentUser.app_role.globalAccess; - - const organizationId = req.currentUser.organization?.id; - - const payload = await UsersDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - globalAccess, - organizationId, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/users/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Users] - * summary: Get selected item - * description: Get selected item - * parameters: - * - in: path - * name: id - * description: ID of item to get - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Selected item successfully received - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Users" - * 400: - * description: Invalid ID supplied - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Item not found - * 500: - * description: Some server error - */ -router.get( - '/:id', - wrapAsync(async (req, res) => { - const payload = await UsersDBApi.findBy({ id: req.params.id }); - - delete payload.password; - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/services/attendances.js b/backend/src/services/attendances.js deleted file mode 100644 index 657e087..0000000 --- a/backend/src/services/attendances.js +++ /dev/null @@ -1,114 +0,0 @@ -const db = require('../db/models'); -const AttendancesDBApi = require('../db/api/attendances'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); -const stream = require('stream'); - -module.exports = class AttendancesService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await AttendancesDBApi.create(data, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async bulkImport(req, res, sendInvitationEmails = true, host) { - const transaction = await db.sequelize.transaction(); - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', async () => { - console.log('CSV results', results); - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - await AttendancesDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - let attendances = await AttendancesDBApi.findBy({ id }, { transaction }); - - if (!attendances) { - throw new ValidationError('attendancesNotFound'); - } - - const updatedAttendances = await AttendancesDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedAttendances; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await AttendancesDBApi.deleteByIds(ids, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await AttendancesDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/backend/src/services/payrolls.js b/backend/src/services/payrolls.js deleted file mode 100644 index 68fcf21..0000000 --- a/backend/src/services/payrolls.js +++ /dev/null @@ -1,114 +0,0 @@ -const db = require('../db/models'); -const PayrollsDBApi = require('../db/api/payrolls'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); -const stream = require('stream'); - -module.exports = class PayrollsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await PayrollsDBApi.create(data, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async bulkImport(req, res, sendInvitationEmails = true, host) { - const transaction = await db.sequelize.transaction(); - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', async () => { - console.log('CSV results', results); - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - await PayrollsDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - let payrolls = await PayrollsDBApi.findBy({ id }, { transaction }); - - if (!payrolls) { - throw new ValidationError('payrollsNotFound'); - } - - const updatedPayrolls = await PayrollsDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedPayrolls; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await PayrollsDBApi.deleteByIds(ids, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await PayrollsDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/backend/src/services/permissions.js b/backend/src/services/permissions.js deleted file mode 100644 index ad78c26..0000000 --- a/backend/src/services/permissions.js +++ /dev/null @@ -1,114 +0,0 @@ -const db = require('../db/models'); -const PermissionsDBApi = require('../db/api/permissions'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); -const stream = require('stream'); - -module.exports = class PermissionsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await PermissionsDBApi.create(data, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async bulkImport(req, res, sendInvitationEmails = true, host) { - const transaction = await db.sequelize.transaction(); - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', async () => { - console.log('CSV results', results); - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - await PermissionsDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - let permissions = await PermissionsDBApi.findBy({ id }, { transaction }); - - if (!permissions) { - throw new ValidationError('permissionsNotFound'); - } - - const updatedPermissions = await PermissionsDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedPermissions; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await PermissionsDBApi.deleteByIds(ids, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await PermissionsDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/backend/src/services/roles.js b/backend/src/services/roles.js deleted file mode 100644 index eeef902..0000000 --- a/backend/src/services/roles.js +++ /dev/null @@ -1,391 +0,0 @@ -const db = require('../db/models'); -const RolesDBApi = require('../db/api/roles'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); -const stream = require('stream'); - -function buildWidgetResult(widget, queryResult, queryString) { - if (queryResult[0] && queryResult[0].length) { - const key = Object.keys(queryResult[0][0])[0]; - const value = - widget.widget_type === 'scalar' ? queryResult[0][0][key] : queryResult[0]; - const widgetData = JSON.parse(widget.data); - return { ...widget, ...widgetData, value, query: queryString }; - } else { - return { ...widget, value: [], query: queryString }; - } -} - -async function executeQuery(queryString, currentUser) { - try { - return await db.sequelize.query(queryString, { - replacements: { organizationId: currentUser.organizationId }, - }); - } catch (e) { - console.log(e); - return []; - } -} - -function insertWhereConditions(queryString, whereConditions) { - if (!whereConditions) return queryString; - - const whereIndex = queryString.toLowerCase().indexOf('where'); - const groupByIndex = queryString.toLowerCase().indexOf('group by'); - const insertIndex = - whereIndex === -1 - ? groupByIndex !== -1 - ? groupByIndex - : queryString.length - : whereIndex + 5; - - const prefix = queryString.substring(0, insertIndex); - const suffix = queryString.substring(insertIndex); - const conditionString = - whereIndex === -1 - ? ` WHERE ${whereConditions} ` - : ` ${whereConditions} AND `; - - return `${prefix}${conditionString}${suffix}`; -} - -function constructWhereConditions(mainTable, currentUser, replacements) { - const { - organizationId, - app_role: { globalAccess }, - } = currentUser; - const tablesWithoutOrgId = ['permissions', 'roles']; - let whereConditions = ''; - - if (!globalAccess && !tablesWithoutOrgId.includes(mainTable)) { - whereConditions += `"${mainTable}"."organizationId" = :organizationId`; - replacements.organizationId = organizationId; - } - - if (mainTable !== 'users') { - whereConditions += whereConditions ? ' AND ' : ''; - whereConditions += `"${mainTable}"."deletedAt" IS NULL`; - } - - return whereConditions; -} - -function extractTableName(queryString) { - const tableNameRegex = /FROM\s+("?)([^"\s]+)\1\s*/i; - const match = tableNameRegex.exec(queryString); - return match ? match[2] : null; -} - -function buildQueryString(widget, currentUser) { - let queryString = widget?.query || ''; - const tableName = extractTableName(queryString); - const mainTable = JSON.parse(widget?.data)?.main_table || tableName; - const replacements = {}; - const whereConditions = constructWhereConditions( - mainTable, - currentUser, - replacements, - ); - queryString = insertWhereConditions(queryString, whereConditions); - console.log(queryString, 'queryString'); - return queryString; -} - -async function constructWidgetsResults(widgets, currentUser) { - const widgetsResults = []; - for (const widget of widgets) { - if (!widget) continue; - const queryString = buildQueryString(widget, currentUser); - const queryResult = await executeQuery(queryString, currentUser); - widgetsResults.push(buildWidgetResult(widget, queryResult, queryString)); - } - return widgetsResults; -} - -async function fetchWidgetsData(widgets) { - const widgetPromises = (widgets || []).map((widgetId) => - axios.get( - `${config.flHost}/${config.project_uuid}/project_customization_widgets/${widgetId}.json`, - ), - ); - const widgetResults = widgetPromises - ? await Promise.allSettled(widgetPromises) - : []; - return widgetResults - .filter((result) => result.status === 'fulfilled') - .map((result) => result.value.data); -} - -async function processWidgets(widgets, currentUser) { - const widgetData = await fetchWidgetsData(widgets); - return constructWidgetsResults(widgetData, currentUser); -} - -function parseCustomization(role) { - try { - return JSON.parse(role.role_customization || '{}'); - } catch (e) { - console.log(e); - return {}; - } -} - -async function findRole(roleId, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - const role = roleId - ? await RolesDBApi.findBy({ id: roleId }, { transaction }) - : await RolesDBApi.findBy({ name: 'User' }, { transaction }); - await transaction.commit(); - return role; - } catch (error) { - await transaction.rollback(); - throw error; - } -} - -module.exports = class RolesService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await RolesDBApi.create(data, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async bulkImport(req, res, sendInvitationEmails = true, host) { - const transaction = await db.sequelize.transaction(); - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', async () => { - console.log('CSV results', results); - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - await RolesDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - let roles = await RolesDBApi.findBy({ id }, { transaction }); - - if (!roles) { - throw new ValidationError('rolesNotFound'); - } - - const updatedRoles = await RolesDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedRoles; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await RolesDBApi.deleteByIds(ids, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await RolesDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async addRoleInfo(roleId, userId, key, widgetId, currentUser) { - const regexExpForUuid = - /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi; - const widgetIdIsUUID = regexExpForUuid.test(widgetId); - - const transaction = await db.sequelize.transaction(); - let role; - if (roleId) { - role = await RolesDBApi.findBy({ id: roleId }, { transaction }); - } else { - role = await RolesDBApi.findBy({ name: 'User' }, { transaction }); - } - - if (!role) { - throw new ValidationError('rolesNotFound'); - } - - try { - let customization = {}; - try { - customization = JSON.parse(role.role_customization || '{}'); - } catch (e) { - console.log(e); - } - - if (widgetIdIsUUID && Array.isArray(customization[key])) { - const el = customization[key].find((e) => e === widgetId); - !el ? customization[key].unshift(widgetId) : null; - } - - if (widgetIdIsUUID && !customization[key]) { - customization[key] = [widgetId]; - } - - const newRole = await RolesDBApi.update( - role.id, - { - role_customization: JSON.stringify(customization), - name: role.name, - permissions: role.permissions, - globalAccess: role.globalAccess, - }, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - - return newRole; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async removeRoleInfoById(infoId, roleId, key, currentUser) { - const transaction = await db.sequelize.transaction(); - - let role; - if (roleId) { - role = await RolesDBApi.findBy({ id: roleId }, { transaction }); - } else { - role = await RolesDBApi.findBy({ name: 'User' }, { transaction }); - } - if (!role) { - await transaction.rollback(); - throw new ValidationError('rolesNotFound'); - } - - let customization = {}; - try { - customization = JSON.parse(role.role_customization || '{}'); - } catch (e) { - console.log(e); - } - - customization[key] = customization[key].filter((item) => item !== infoId); - - const response = await axios.delete( - `${config.flHost}/${config.project_uuid}/project_customization_widgets/${infoId}.json`, - ); - const { status } = await response; - try { - const result = await RolesDBApi.update( - role.id, - { - role_customization: JSON.stringify(customization), - name: role.name, - permissions: role.permissions, - globalAccess: role.globalAccess, - }, - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return result; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async getRoleInfoByKey(key, roleId, currentUser) { - const transaction = await db.sequelize.transaction(); - - const organizationId = currentUser.organizationId; - let globalAccess = currentUser.app_role?.globalAccess; - let queryString = ''; - - try { - const role = await findRole(roleId, currentUser); - const customization = parseCustomization(role); - - let result; - if (key === 'widgets') { - result = await processWidgets(customization[key], currentUser); - } else { - result = customization[key]; - } - - await transaction.commit(); - return result; - } catch (error) { - console.error(error); - await transaction.rollback(); - } finally { - if (transaction.finished !== 'commit') { - await transaction.rollback(); - } - } - } -}; diff --git a/backend/src/services/search.js b/backend/src/services/search.js index 012f9c0..9e79deb 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -41,73 +41,9 @@ module.exports = class SearchService { throw new ValidationError('iam.errors.searchQueryRequired'); } const tableColumns = { - users: ['firstName', 'lastName', 'phoneNumber', 'email'], - - attendances: ['gps_location'], - - employees: [ - 'first_name', - - 'middle_name', - - 'last_name', - - 'aadhar_number', - - 'pan_number', - - 'cell_phone_number', - - 'current_residential_address', - - 'permanent_residential_address', - - 'designation', - - 'department', - - 'reporting_manager', - ], - organizations: ['name'], }; - const columnsInt = { - attendances: ['total_hours'], - - employees: [ - 'basic_salary', - - 'hra', - - 'allowance_1', - - 'allowance_2', - - 'allowance_3', - - 'advance_paid_1', - - 'advance_paid_2', - - 'employer_pf_contribution', - - 'employee_pf_contribution', - - 'professional_tax', - - 'advances_1', - - 'advances_2', - - 'deduction_1', - - 'deduction_2', - - 'allowed_leaves', - ], - - payrolls: ['total_salary'], - }; + const columnsInt = {}; let allFoundRecords = []; diff --git a/backend/src/services/users.js b/backend/src/services/users.js deleted file mode 100644 index e75abfb..0000000 --- a/backend/src/services/users.js +++ /dev/null @@ -1,163 +0,0 @@ -const db = require('../db/models'); -const UsersDBApi = require('../db/api/users'); -const processFile = require('../middlewares/upload'); -const ValidationError = require('./notifications/errors/validation'); -const csv = require('csv-parser'); -const axios = require('axios'); -const config = require('../config'); -const stream = require('stream'); - -const InvitationEmail = require('./email/list/invitation'); -const EmailSender = require('./email'); -const AuthService = require('./auth'); - -module.exports = class UsersService { - static async create(data, currentUser, sendInvitationEmails = true, host) { - let transaction = await db.sequelize.transaction(); - - const globalAccess = currentUser.app_role.globalAccess; - - let email = data.email; - let emailsToInvite = []; - try { - if (email) { - let user = await UsersDBApi.findBy({ email }, { transaction }); - if (user) { - throw new ValidationError('iam.errors.userAlreadyExists'); - } else { - await UsersDBApi.create( - { data }, - - globalAccess, - - { - currentUser, - transaction, - }, - ); - emailsToInvite.push(email); - } - } else { - throw new ValidationError('iam.errors.emailRequired'); - } - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - if (emailsToInvite && emailsToInvite.length) { - if (!sendInvitationEmails) return; - - AuthService.sendPasswordResetEmail(email, 'invitation', host); - } - } - - static async bulkImport(req, res, sendInvitationEmails = true, host) { - const transaction = await db.sequelize.transaction(); - let emailsToInvite = []; - - try { - await processFile(req, res); - const bufferStream = new stream.PassThrough(); - const results = []; - - await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream - - await new Promise((resolve, reject) => { - bufferStream - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', () => { - console.log('results csv', results); - resolve(); - }) - .on('error', (error) => reject(error)); - }); - - const hasAllEmails = results.every((result) => result.email); - - if (!hasAllEmails) { - throw new ValidationError('importer.errors.userEmailMissing'); - } - - await UsersDBApi.bulkImport(results, { - transaction, - ignoreDuplicates: true, - validate: true, - currentUser: req.currentUser, - }); - - emailsToInvite = results.map((result) => result.email); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - - if (emailsToInvite && emailsToInvite.length && !sendInvitationEmails) { - emailsToInvite.forEach((email) => { - AuthService.sendPasswordResetEmail(email, 'invitation', host); - }); - } - } - - static async update(data, id, currentUser) { - const transaction = await db.sequelize.transaction(); - - const globalAccess = currentUser.app_role.globalAccess; - - try { - let users = await UsersDBApi.findBy({ id }, { transaction }); - - if (!users) { - throw new ValidationError('iam.errors.userNotFound'); - } - - const updatedUser = await UsersDBApi.update( - id, - data, - - globalAccess, - - { - currentUser, - transaction, - }, - ); - - await transaction.commit(); - return updatedUser; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async remove(id, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - if (currentUser.id === id) { - throw new ValidationError('iam.errors.deletingHimself'); - } - - if ( - currentUser.app_role?.name !== config.roles.admin && - currentUser.app_role?.name !== config.roles.super_admin - ) { - throw new ValidationError('errors.forbidden.message'); - } - - await UsersDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/frontend/json/runtimeError.json b/frontend/json/runtimeError.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/frontend/json/runtimeError.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/frontend/src/components/Attendances/CardAttendances.tsx b/frontend/src/components/Attendances/CardAttendances.tsx deleted file mode 100644 index 2d963fc..0000000 --- a/frontend/src/components/Attendances/CardAttendances.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - attendances: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardAttendances = ({ - attendances, - 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_ATTENDANCES'); - - return ( -
- {loading && } -
    - {!loading && - attendances.map((item, index) => ( -
  • -
    - - -

    - {item.gps_location} -

    - - -
    - -
    -
    -
    -
    -
    - TimeIn -
    -
    -
    - {dataFormatter.dateTimeFormatter(item.time_in)} -
    -
    -
    - -
    -
    - TimeOut -
    -
    -
    - {dataFormatter.dateTimeFormatter(item.time_out)} -
    -
    -
    - -
    -
    - TotalHoursfortheDay -
    -
    -
    - {item.total_hours} -
    -
    -
    - -
    -
    - GPSLocation -
    -
    -
    - {item.gps_location} -
    -
    -
    - -
    -
    - Selfie -
    -
    -
    - -
    -
    -
    - -
    -
    - Employee -
    -
    -
    - {dataFormatter.employeesOneListFormatter(item.employee)} -
    -
    -
    -
    -
  • - ))} - {!loading && attendances.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardAttendances; diff --git a/frontend/src/components/Attendances/ListAttendances.tsx b/frontend/src/components/Attendances/ListAttendances.tsx deleted file mode 100644 index b05d456..0000000 --- a/frontend/src/components/Attendances/ListAttendances.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import { saveFile } from '../../helpers/fileSaver'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import { Pagination } from '../Pagination'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - attendances: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListAttendances = ({ - attendances, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ATTENDANCES'); - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - return ( - <> -
- {loading && } - {!loading && - attendances.map((item) => ( -
- -
- - - dark:divide-dark-700 overflow-x-auto' - } - > -
-

TimeIn

-

- {dataFormatter.dateTimeFormatter(item.time_in)} -

-
- -
-

TimeOut

-

- {dataFormatter.dateTimeFormatter(item.time_out)} -

-
- -
-

- TotalHoursfortheDay -

-

{item.total_hours}

-
- -
-

GPSLocation

-

{item.gps_location}

-
- -
-

Selfie

- -
- -
-

Employee

-

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

-
- - -
-
-
- ))} - {!loading && attendances.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListAttendances; diff --git a/frontend/src/components/Attendances/TableAttendances.tsx b/frontend/src/components/Attendances/TableAttendances.tsx deleted file mode 100644 index 498a409..0000000 --- a/frontend/src/components/Attendances/TableAttendances.tsx +++ /dev/null @@ -1,513 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import CardBox from '../CardBox'; -import { - fetch, - update, - deleteItem, - setRefetch, - deleteItemsByIds, -} from '../../stores/attendances/attendancesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { Field, Form, Formik } from 'formik'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { loadColumns } from './configureAttendancesCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -import BigCalendar from '../BigCalendar'; -import { SlotInfo } from 'react-big-calendar'; - -const perPage = 100; - -const TableSampleAttendances = ({ - filterItems, - setFilterItems, - filters, - showGrid, -}) => { - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { - attendances, - loading, - count, - notify: attendancesNotify, - refetch, - } = useAppSelector((state) => state.attendances); - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = - Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (attendancesNotify.showNotification) { - notify( - attendancesNotify.typeNotification, - attendancesNotify.textNotification, - ); - } - }, [attendancesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false); - const [isModalTrashActive, setIsModalTrashActive] = useState(false); - - const handleModalAction = () => { - setIsModalInfoActive(false); - setIsModalTrashActive(false); - }; - - const handleCreateEventAction = ({ start, end }: SlotInfo) => { - router.push( - `/attendances/attendances-new?dateRangeStart=${start.toISOString()}&dateRangeEnd=${end.toISOString()}`, - ); - }; - - const handleDeleteModalAction = (id: string) => { - setId(id); - setIsModalTrashActive(true); - }; - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } }; - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - useEffect(() => { - if (!currentUser) return; - - loadColumns(handleDeleteModalAction, `attendances`, currentUser).then( - (newCols) => setColumns(newCols), - ); - }, [currentUser]); - - const handleTableSubmit = async (id: string, data) => { - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - const dataGrid = ( -
- `datagrid--row`} - rows={attendances ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids); - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ); - - return ( - <> - {filterItems && Array.isArray(filterItems) && filterItems.length ? ( - - null} - > -
- <> - {filterItems && - filterItems.map((filterItem) => { - return ( -
-
-
- Filter -
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find( - (filter) => - filter.title === filterItem?.fields?.selectedField, - )?.type === 'enum' ? ( -
-
Value
- - - {filters - .find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - ) - ?.options?.map((option) => ( - - ))} - -
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.number ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.date ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : ( -
-
- Contains -
- -
- )} -
-
- Action -
- { - deleteFilter(filterItem.id); - }} - /> -
-
- ); - })} -
- - -
- -
-
-
- ) : null} - -

Are you sure you want to delete this item?

-
- - {!showGrid && ( - { - loadData( - 0, - `&calendarStart=${range.start}&calendarEnd=${range.end}`, - ); - }} - entityName={'attendances'} - /> - )} - - {showGrid && dataGrid} - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ); -}; - -export default TableSampleAttendances; diff --git a/frontend/src/components/Attendances/configureAttendancesCols.tsx b/frontend/src/components/Attendances/configureAttendancesCols.tsx deleted file mode 100644 index db3e1e5..0000000 --- a/frontend/src/components/Attendances/configureAttendancesCols.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user, -) => { - async function callOptionsApi(entityName: string) { - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ATTENDANCES'); - - return [ - { - field: 'time_in', - headerName: 'TimeIn', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.time_in), - }, - - { - field: 'time_out', - headerName: 'TimeOut', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'dateTime', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.time_out), - }, - - { - field: 'total_hours', - headerName: 'TotalHoursfortheDay', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'gps_location', - headerName: 'GPSLocation', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'selfie', - headerName: 'Selfie', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - - ), - }, - - { - field: 'employee', - headerName: 'Employee', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('employees'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ -
- -
, - ]; - }, - }, - ]; -}; diff --git a/frontend/src/components/Payrolls/CardPayrolls.tsx b/frontend/src/components/Payrolls/CardPayrolls.tsx deleted file mode 100644 index e6ed6ac..0000000 --- a/frontend/src/components/Payrolls/CardPayrolls.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - payrolls: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardPayrolls = ({ - payrolls, - 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_PAYROLLS'); - - return ( -
- {loading && } -
    - {!loading && - payrolls.map((item, index) => ( -
  • -
    - - {item.total_salary} - - -
    - -
    -
    -
    -
    -
    - TotalSalary -
    -
    -
    - {item.total_salary} -
    -
    -
    - -
    -
    - Employee -
    -
    -
    - {dataFormatter.employeesOneListFormatter(item.employee)} -
    -
    -
    -
    -
  • - ))} - {!loading && payrolls.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardPayrolls; diff --git a/frontend/src/components/Payrolls/ListPayrolls.tsx b/frontend/src/components/Payrolls/ListPayrolls.tsx deleted file mode 100644 index b39e763..0000000 --- a/frontend/src/components/Payrolls/ListPayrolls.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import { saveFile } from '../../helpers/fileSaver'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import { Pagination } from '../Pagination'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - payrolls: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListPayrolls = ({ - payrolls, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PAYROLLS'); - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - return ( - <> -
- {loading && } - {!loading && - payrolls.map((item) => ( -
- -
- dark:divide-dark-700 overflow-x-auto' - } - > -
-

TotalSalary

-

{item.total_salary}

-
- -
-

Employee

-

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

-
- - -
-
-
- ))} - {!loading && payrolls.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListPayrolls; diff --git a/frontend/src/components/Payrolls/TablePayrolls.tsx b/frontend/src/components/Payrolls/TablePayrolls.tsx deleted file mode 100644 index 34937b4..0000000 --- a/frontend/src/components/Payrolls/TablePayrolls.tsx +++ /dev/null @@ -1,494 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import CardBox from '../CardBox'; -import { - fetch, - update, - deleteItem, - setRefetch, - deleteItemsByIds, -} from '../../stores/payrolls/payrollsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { Field, Form, Formik } from 'formik'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { loadColumns } from './configurePayrollsCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -import ListPayrolls from './ListPayrolls'; - -const perPage = 10; - -const TableSamplePayrolls = ({ - filterItems, - setFilterItems, - filters, - showGrid, -}) => { - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { - payrolls, - loading, - count, - notify: payrollsNotify, - refetch, - } = useAppSelector((state) => state.payrolls); - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = - Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (payrollsNotify.showNotification) { - notify(payrollsNotify.typeNotification, payrollsNotify.textNotification); - } - }, [payrollsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false); - const [isModalTrashActive, setIsModalTrashActive] = useState(false); - - const handleModalAction = () => { - setIsModalInfoActive(false); - setIsModalTrashActive(false); - }; - - const handleDeleteModalAction = (id: string) => { - setId(id); - setIsModalTrashActive(true); - }; - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } }; - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - useEffect(() => { - if (!currentUser) return; - - loadColumns(handleDeleteModalAction, `payrolls`, currentUser).then( - (newCols) => setColumns(newCols), - ); - }, [currentUser]); - - const handleTableSubmit = async (id: string, data) => { - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - const dataGrid = ( -
- `datagrid--row`} - rows={payrolls ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids); - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ); - - return ( - <> - {filterItems && Array.isArray(filterItems) && filterItems.length ? ( - - null} - > -
- <> - {filterItems && - filterItems.map((filterItem) => { - return ( -
-
-
- Filter -
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find( - (filter) => - filter.title === filterItem?.fields?.selectedField, - )?.type === 'enum' ? ( -
-
Value
- - - {filters - .find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - ) - ?.options?.map((option) => ( - - ))} - -
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.number ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.date ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : ( -
-
- Contains -
- -
- )} -
-
- Action -
- { - deleteFilter(filterItem.id); - }} - /> -
-
- ); - })} -
- - -
- -
-
-
- ) : null} - -

Are you sure you want to delete this item?

-
- - {payrolls && Array.isArray(payrolls) && !showGrid && ( - - )} - - {showGrid && dataGrid} - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ); -}; - -export default TableSamplePayrolls; diff --git a/frontend/src/components/Payrolls/configurePayrollsCols.tsx b/frontend/src/components/Payrolls/configurePayrollsCols.tsx deleted file mode 100644 index 63c1a7b..0000000 --- a/frontend/src/components/Payrolls/configurePayrollsCols.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user, -) => { - async function callOptionsApi(entityName: string) { - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PAYROLLS'); - - return [ - { - field: 'total_salary', - headerName: 'TotalSalary', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'employee', - headerName: 'Employee', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - sortable: false, - type: 'singleSelect', - getOptionValue: (value: any) => value?.id, - getOptionLabel: (value: any) => value?.label, - valueOptions: await callOptionsApi('employees'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ -
- -
, - ]; - }, - }, - ]; -}; diff --git a/frontend/src/components/Permissions/CardPermissions.tsx b/frontend/src/components/Permissions/CardPermissions.tsx deleted file mode 100644 index 1948252..0000000 --- a/frontend/src/components/Permissions/CardPermissions.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - permissions: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardPermissions = ({ - permissions, - 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_PERMISSIONS'); - - return ( -
- {loading && } -
    - {!loading && - permissions.map((item, index) => ( -
  • -
    - - {item.name} - - -
    - -
    -
    -
    -
    -
    Name
    -
    -
    {item.name}
    -
    -
    -
    -
  • - ))} - {!loading && permissions.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardPermissions; diff --git a/frontend/src/components/Permissions/ListPermissions.tsx b/frontend/src/components/Permissions/ListPermissions.tsx deleted file mode 100644 index b74b0a0..0000000 --- a/frontend/src/components/Permissions/ListPermissions.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import { saveFile } from '../../helpers/fileSaver'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import { Pagination } from '../Pagination'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - permissions: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListPermissions = ({ - permissions, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERMISSIONS'); - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - return ( - <> -
- {loading && } - {!loading && - permissions.map((item) => ( -
- -
- dark:divide-dark-700 overflow-x-auto' - } - > -
-

Name

-

{item.name}

-
- - -
-
-
- ))} - {!loading && permissions.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListPermissions; diff --git a/frontend/src/components/Permissions/TablePermissions.tsx b/frontend/src/components/Permissions/TablePermissions.tsx deleted file mode 100644 index 21ac91c..0000000 --- a/frontend/src/components/Permissions/TablePermissions.tsx +++ /dev/null @@ -1,484 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import CardBox from '../CardBox'; -import { - fetch, - update, - deleteItem, - setRefetch, - deleteItemsByIds, -} from '../../stores/permissions/permissionsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { Field, Form, Formik } from 'formik'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { loadColumns } from './configurePermissionsCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -const perPage = 10; - -const TableSamplePermissions = ({ - filterItems, - setFilterItems, - filters, - showGrid, -}) => { - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { - permissions, - loading, - count, - notify: permissionsNotify, - refetch, - } = useAppSelector((state) => state.permissions); - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = - Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (permissionsNotify.showNotification) { - notify( - permissionsNotify.typeNotification, - permissionsNotify.textNotification, - ); - } - }, [permissionsNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false); - const [isModalTrashActive, setIsModalTrashActive] = useState(false); - - const handleModalAction = () => { - setIsModalInfoActive(false); - setIsModalTrashActive(false); - }; - - const handleDeleteModalAction = (id: string) => { - setId(id); - setIsModalTrashActive(true); - }; - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } }; - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - useEffect(() => { - if (!currentUser) return; - - loadColumns(handleDeleteModalAction, `permissions`, currentUser).then( - (newCols) => setColumns(newCols), - ); - }, [currentUser]); - - const handleTableSubmit = async (id: string, data) => { - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - const dataGrid = ( -
- `datagrid--row`} - rows={permissions ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids); - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ); - - return ( - <> - {filterItems && Array.isArray(filterItems) && filterItems.length ? ( - - null} - > -
- <> - {filterItems && - filterItems.map((filterItem) => { - return ( -
-
-
- Filter -
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find( - (filter) => - filter.title === filterItem?.fields?.selectedField, - )?.type === 'enum' ? ( -
-
Value
- - - {filters - .find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - ) - ?.options?.map((option) => ( - - ))} - -
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.number ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.date ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : ( -
-
- Contains -
- -
- )} -
-
- Action -
- { - deleteFilter(filterItem.id); - }} - /> -
-
- ); - })} -
- - -
- -
-
-
- ) : null} - -

Are you sure you want to delete this item?

-
- - {dataGrid} - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ); -}; - -export default TableSamplePermissions; diff --git a/frontend/src/components/Permissions/configurePermissionsCols.tsx b/frontend/src/components/Permissions/configurePermissionsCols.tsx deleted file mode 100644 index 2d46f88..0000000 --- a/frontend/src/components/Permissions/configurePermissionsCols.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user, -) => { - async function callOptionsApi(entityName: string) { - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_PERMISSIONS'); - - return [ - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ -
- -
, - ]; - }, - }, - ]; -}; diff --git a/frontend/src/components/Roles/CardRoles.tsx b/frontend/src/components/Roles/CardRoles.tsx deleted file mode 100644 index 86d10a4..0000000 --- a/frontend/src/components/Roles/CardRoles.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - roles: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardRoles = ({ - roles, - 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_ROLES'); - - return ( -
- {loading && } -
    - {!loading && - roles.map((item, index) => ( -
  • -
    - - {item.name} - - -
    - -
    -
    -
    -
    -
    Name
    -
    -
    {item.name}
    -
    -
    - -
    -
    - Permissions -
    -
    -
    - {dataFormatter - .permissionsManyListFormatter(item.permissions) - .join(', ')} -
    -
    -
    - -
    -
    - Global Access -
    -
    -
    - {dataFormatter.booleanFormatter(item.globalAccess)} -
    -
    -
    -
    -
  • - ))} - {!loading && roles.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardRoles; diff --git a/frontend/src/components/Roles/ListRoles.tsx b/frontend/src/components/Roles/ListRoles.tsx deleted file mode 100644 index ef2de05..0000000 --- a/frontend/src/components/Roles/ListRoles.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import { saveFile } from '../../helpers/fileSaver'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import { Pagination } from '../Pagination'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - roles: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListRoles = ({ - roles, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_ROLES'); - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - return ( - <> -
- {loading && } - {!loading && - roles.map((item) => ( -
- -
- dark:divide-dark-700 overflow-x-auto' - } - > -
-

Name

-

{item.name}

-
- -
-

Permissions

-

- {dataFormatter - .permissionsManyListFormatter(item.permissions) - .join(', ')} -

-
- -
-

- Global Access -

-

- {dataFormatter.booleanFormatter(item.globalAccess)} -

-
- - -
-
-
- ))} - {!loading && roles.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListRoles; diff --git a/frontend/src/components/Roles/TableRoles.tsx b/frontend/src/components/Roles/TableRoles.tsx deleted file mode 100644 index 701b6e3..0000000 --- a/frontend/src/components/Roles/TableRoles.tsx +++ /dev/null @@ -1,481 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import CardBox from '../CardBox'; -import { - fetch, - update, - deleteItem, - setRefetch, - deleteItemsByIds, -} from '../../stores/roles/rolesSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { Field, Form, Formik } from 'formik'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { loadColumns } from './configureRolesCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -const perPage = 10; - -const TableSampleRoles = ({ - filterItems, - setFilterItems, - filters, - showGrid, -}) => { - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { - roles, - loading, - count, - notify: rolesNotify, - refetch, - } = useAppSelector((state) => state.roles); - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = - Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (rolesNotify.showNotification) { - notify(rolesNotify.typeNotification, rolesNotify.textNotification); - } - }, [rolesNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false); - const [isModalTrashActive, setIsModalTrashActive] = useState(false); - - const handleModalAction = () => { - setIsModalInfoActive(false); - setIsModalTrashActive(false); - }; - - const handleDeleteModalAction = (id: string) => { - setId(id); - setIsModalTrashActive(true); - }; - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } }; - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - useEffect(() => { - if (!currentUser) return; - - loadColumns(handleDeleteModalAction, `roles`, currentUser).then((newCols) => - setColumns(newCols), - ); - }, [currentUser]); - - const handleTableSubmit = async (id: string, data) => { - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - const dataGrid = ( -
- `datagrid--row`} - rows={roles ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids); - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ); - - return ( - <> - {filterItems && Array.isArray(filterItems) && filterItems.length ? ( - - null} - > -
- <> - {filterItems && - filterItems.map((filterItem) => { - return ( -
-
-
- Filter -
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find( - (filter) => - filter.title === filterItem?.fields?.selectedField, - )?.type === 'enum' ? ( -
-
Value
- - - {filters - .find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - ) - ?.options?.map((option) => ( - - ))} - -
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.number ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.date ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : ( -
-
- Contains -
- -
- )} -
-
- Action -
- { - deleteFilter(filterItem.id); - }} - /> -
-
- ); - })} -
- - -
- -
-
-
- ) : null} - -

Are you sure you want to delete this item?

-
- - {dataGrid} - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ); -}; - -export default TableSampleRoles; diff --git a/frontend/src/components/Roles/configureRolesCols.tsx b/frontend/src/components/Roles/configureRolesCols.tsx deleted file mode 100644 index ce3fbf1..0000000 --- a/frontend/src/components/Roles/configureRolesCols.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user, -) => { - async function callOptionsApi(entityName: string) { - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_ROLES'); - - return [ - { - field: 'name', - headerName: 'Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'permissions', - headerName: 'Permissions', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - type: 'singleSelect', - valueFormatter: ({ value }) => - dataFormatter.permissionsManyListFormatter(value).join(', '), - renderEditCell: (params) => ( - - ), - }, - - { - field: 'globalAccess', - headerName: 'Global Access', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'boolean', - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ -
- -
, - ]; - }, - }, - ]; -}; diff --git a/frontend/src/components/Users/CardUsers.tsx b/frontend/src/components/Users/CardUsers.tsx deleted file mode 100644 index 3a72199..0000000 --- a/frontend/src/components/Users/CardUsers.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import React from 'react'; -import ImageField from '../ImageField'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import dataFormatter from '../../helpers/dataFormatter'; -import { Pagination } from '../Pagination'; -import { saveFile } from '../../helpers/fileSaver'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - users: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardUsers = ({ - users, - 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_USERS'); - - return ( -
- {loading && } -
    - {!loading && - users.map((item, index) => ( -
  • -
    - - -

    {item.firstName}

    - - -
    - -
    -
    -
    -
    -
    - First Name -
    -
    -
    - {item.firstName} -
    -
    -
    - -
    -
    - Last Name -
    -
    -
    - {item.lastName} -
    -
    -
    - -
    -
    - Phone Number -
    -
    -
    - {item.phoneNumber} -
    -
    -
    - -
    -
    - E-Mail -
    -
    -
    {item.email}
    -
    -
    - -
    -
    - Disabled -
    -
    -
    - {dataFormatter.booleanFormatter(item.disabled)} -
    -
    -
    - -
    -
    - Avatar -
    -
    -
    - -
    -
    -
    - -
    -
    - App Role -
    -
    -
    - {dataFormatter.rolesOneListFormatter(item.app_role)} -
    -
    -
    - -
    -
    - Custom Permissions -
    -
    -
    - {dataFormatter - .permissionsManyListFormatter(item.custom_permissions) - .join(', ')} -
    -
    -
    - -
    -
    - Organizations -
    -
    -
    - {dataFormatter.organizationsOneListFormatter( - item.organizations, - )} -
    -
    -
    -
    -
  • - ))} - {!loading && users.length === 0 && ( -
    -

    No data to display

    -
    - )} -
-
- -
-
- ); -}; - -export default CardUsers; diff --git a/frontend/src/components/Users/ListUsers.tsx b/frontend/src/components/Users/ListUsers.tsx deleted file mode 100644 index e735124..0000000 --- a/frontend/src/components/Users/ListUsers.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import React from 'react'; -import CardBox from '../CardBox'; -import ImageField from '../ImageField'; -import dataFormatter from '../../helpers/dataFormatter'; -import { saveFile } from '../../helpers/fileSaver'; -import ListActionsPopover from '../ListActionsPopover'; -import { useAppSelector } from '../../stores/hooks'; -import { Pagination } from '../Pagination'; -import LoadingSpinner from '../LoadingSpinner'; -import Link from 'next/link'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Props = { - users: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const ListUsers = ({ - users, - loading, - onDelete, - currentPage, - numPages, - onPageChange, -}: Props) => { - const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_USERS'); - - const corners = useAppSelector((state) => state.style.corners); - const bgColor = useAppSelector((state) => state.style.cardsColor); - - return ( - <> -
- {loading && } - {!loading && - users.map((item) => ( -
- -
- - - dark:divide-dark-700 overflow-x-auto' - } - > -
-

First Name

-

{item.firstName}

-
- -
-

Last Name

-

{item.lastName}

-
- -
-

Phone Number

-

{item.phoneNumber}

-
- -
-

E-Mail

-

{item.email}

-
- -
-

Disabled

-

- {dataFormatter.booleanFormatter(item.disabled)} -

-
- -
-

Avatar

- -
- -
-

App Role

-

- {dataFormatter.rolesOneListFormatter(item.app_role)} -

-
- -
-

- Custom Permissions -

-

- {dataFormatter - .permissionsManyListFormatter(item.custom_permissions) - .join(', ')} -

-
- -
-

- Organizations -

-

- {dataFormatter.organizationsOneListFormatter( - item.organizations, - )} -

-
- - -
-
-
- ))} - {!loading && users.length === 0 && ( -
-

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListUsers; diff --git a/frontend/src/components/Users/TableUsers.tsx b/frontend/src/components/Users/TableUsers.tsx deleted file mode 100644 index f39c7f5..0000000 --- a/frontend/src/components/Users/TableUsers.tsx +++ /dev/null @@ -1,482 +0,0 @@ -import React, { useEffect, useState, useMemo } from 'react'; -import { createPortal } from 'react-dom'; -import { ToastContainer, toast } from 'react-toastify'; -import BaseButton from '../BaseButton'; -import CardBoxModal from '../CardBoxModal'; -import CardBox from '../CardBox'; -import { - fetch, - update, - deleteItem, - setRefetch, - deleteItemsByIds, -} from '../../stores/users/usersSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { Field, Form, Formik } from 'formik'; -import { DataGrid, GridColDef } from '@mui/x-data-grid'; -import { loadColumns } from './configureUsersCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -const perPage = 10; - -const TableSampleUsers = ({ - filterItems, - setFilterItems, - filters, - showGrid, -}) => { - const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' }); - - const dispatch = useAppDispatch(); - const router = useRouter(); - - const pagesList = []; - const [id, setId] = useState(null); - const [currentPage, setCurrentPage] = useState(0); - const [filterRequest, setFilterRequest] = React.useState(''); - const [columns, setColumns] = useState([]); - const [selectedRows, setSelectedRows] = useState([]); - const [sortModel, setSortModel] = useState([ - { - field: '', - sort: 'desc', - }, - ]); - - const { - users, - loading, - count, - notify: usersNotify, - refetch, - } = useAppSelector((state) => state.users); - const { currentUser } = useAppSelector((state) => state.auth); - const focusRing = useAppSelector((state) => state.style.focusRingColor); - const bgColor = useAppSelector((state) => state.style.bgLayoutColor); - const corners = useAppSelector((state) => state.style.corners); - const numPages = - Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage); - for (let i = 0; i < numPages; i++) { - pagesList.push(i); - } - - const loadData = async (page = currentPage, request = filterRequest) => { - if (page !== currentPage) setCurrentPage(page); - if (request !== filterRequest) setFilterRequest(request); - const { sort, field } = sortModel[0]; - - const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`; - dispatch(fetch({ limit: perPage, page, query })); - }; - - useEffect(() => { - if (usersNotify.showNotification) { - notify(usersNotify.typeNotification, usersNotify.textNotification); - } - }, [usersNotify.showNotification]); - - useEffect(() => { - if (!currentUser) return; - loadData(); - }, [sortModel, currentUser]); - - useEffect(() => { - if (refetch) { - loadData(0); - dispatch(setRefetch(false)); - } - }, [refetch, dispatch]); - - const [isModalInfoActive, setIsModalInfoActive] = useState(false); - const [isModalTrashActive, setIsModalTrashActive] = useState(false); - - const handleModalAction = () => { - setIsModalInfoActive(false); - setIsModalTrashActive(false); - }; - - const handleDeleteModalAction = (id: string) => { - setId(id); - setIsModalTrashActive(true); - }; - const handleDeleteAction = async () => { - if (id) { - await dispatch(deleteItem(id)); - await loadData(0); - setIsModalTrashActive(false); - } - }; - - const generateFilterRequests = useMemo(() => { - let request = '&'; - filterItems.forEach((item) => { - const isRangeFilter = filters.find( - (filter) => - filter.title === item.fields.selectedField && - (filter.number || filter.date), - ); - - if (isRangeFilter) { - const from = item.fields.filterValueFrom; - const to = item.fields.filterValueTo; - if (from) { - request += `${item.fields.selectedField}Range=${from}&`; - } - if (to) { - request += `${item.fields.selectedField}Range=${to}&`; - } - } else { - const value = item.fields.filterValue; - if (value) { - request += `${item.fields.selectedField}=${value}&`; - } - } - }); - return request; - }, [filterItems, filters]); - - const deleteFilter = (value) => { - const newItems = filterItems.filter((item) => item.id !== value); - - if (newItems.length) { - setFilterItems(newItems); - } else { - loadData(0, ''); - - setFilterItems(newItems); - } - }; - - const handleSubmit = () => { - loadData(0, generateFilterRequests); - }; - - const handleChange = (id) => (e) => { - const value = e.target.value; - const name = e.target.name; - - setFilterItems( - filterItems.map((item) => { - if (item.id !== id) return item; - if (name === 'selectedField') return { id, fields: { [name]: value } }; - - return { id, fields: { ...item.fields, [name]: value } }; - }), - ); - }; - - const handleReset = () => { - setFilterItems([]); - loadData(0, ''); - }; - - const onPageChange = (page: number) => { - loadData(page); - setCurrentPage(page); - }; - - useEffect(() => { - if (!currentUser) return; - - loadColumns(handleDeleteModalAction, `users`, currentUser).then((newCols) => - setColumns(newCols), - ); - }, [currentUser]); - - const handleTableSubmit = async (id: string, data) => { - delete data?.password; - if (!_.isEmpty(data)) { - await dispatch(update({ id, data })) - .unwrap() - .then((res) => res) - .catch((err) => { - throw new Error(err); - }); - } - }; - - const onDeleteRows = async (selectedRows) => { - await dispatch(deleteItemsByIds(selectedRows)); - await loadData(0); - }; - - const controlClasses = - 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' + - ` ${bgColor} ${focusRing} ${corners} ` + - 'dark:bg-slate-800 border'; - - const dataGrid = ( -
- `datagrid--row`} - rows={users ?? []} - columns={columns} - initialState={{ - pagination: { - paginationModel: { - pageSize: 10, - }, - }, - }} - disableRowSelectionOnClick - onProcessRowUpdateError={(params) => { - console.log('Error', params); - }} - processRowUpdate={async (newRow, oldRow) => { - const data = dataFormatter.dataGridEditFormatter(newRow); - - try { - await handleTableSubmit(newRow.id, data); - return newRow; - } catch { - return oldRow; - } - }} - sortingMode={'server'} - checkboxSelection - onRowSelectionModelChange={(ids) => { - setSelectedRows(ids); - }} - onSortModelChange={(params) => { - params.length - ? setSortModel(params) - : setSortModel([{ field: '', sort: 'desc' }]); - }} - rowCount={count} - pageSizeOptions={[10]} - paginationMode={'server'} - loading={loading} - onPaginationModelChange={(params) => { - onPageChange(params.page); - }} - /> -
- ); - - return ( - <> - {filterItems && Array.isArray(filterItems) && filterItems.length ? ( - - null} - > -
- <> - {filterItems && - filterItems.map((filterItem) => { - return ( -
-
-
- Filter -
- - {filters.map((selectOption) => ( - - ))} - -
- {filters.find( - (filter) => - filter.title === filterItem?.fields?.selectedField, - )?.type === 'enum' ? ( -
-
Value
- - - {filters - .find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - ) - ?.options?.map((option) => ( - - ))} - -
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.number ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : filters.find( - (filter) => - filter.title === - filterItem?.fields?.selectedField, - )?.date ? ( -
-
-
- From -
- -
-
-
- To -
- -
-
- ) : ( -
-
- Contains -
- -
- )} -
-
- Action -
- { - deleteFilter(filterItem.id); - }} - /> -
-
- ); - })} -
- - -
- -
-
-
- ) : null} - -

Are you sure you want to delete this item?

-
- - {dataGrid} - - {selectedRows.length > 0 && - createPortal( - onDeleteRows(selectedRows)} - />, - document.getElementById('delete-rows-button'), - )} - - - ); -}; - -export default TableSampleUsers; diff --git a/frontend/src/components/Users/configureUsersCols.tsx b/frontend/src/components/Users/configureUsersCols.tsx deleted file mode 100644 index 6fc6b11..0000000 --- a/frontend/src/components/Users/configureUsersCols.tsx +++ /dev/null @@ -1,203 +0,0 @@ -import React from 'react'; -import BaseIcon from '../BaseIcon'; -import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js'; -import axios from 'axios'; -import { - GridActionsCellItem, - GridRowParams, - GridValueGetterParams, -} from '@mui/x-data-grid'; -import ImageField from '../ImageField'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import DataGridMultiSelect from '../DataGridMultiSelect'; -import ListActionsPopover from '../ListActionsPopover'; - -import { hasPermission } from '../../helpers/userPermissions'; - -type Params = (id: string) => void; - -export const loadColumns = async ( - onDelete: Params, - entityName: string, - - user, -) => { - async function callOptionsApi(entityName: string) { - if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return []; - - try { - const data = await axios(`/${entityName}/autocomplete?limit=100`); - return data.data; - } catch (error) { - console.log(error); - return []; - } - } - - const hasUpdatePermission = hasPermission(user, 'UPDATE_USERS'); - - return [ - { - field: 'firstName', - headerName: 'First Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'lastName', - headerName: 'Last Name', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'phoneNumber', - headerName: 'Phone Number', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'email', - headerName: 'E-Mail', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'disabled', - headerName: 'Disabled', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'boolean', - }, - - { - field: 'avatar', - headerName: 'Avatar', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - - ), - }, - - { - field: 'app_role', - headerName: 'App 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: 'custom_permissions', - headerName: 'Custom Permissions', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: false, - sortable: false, - type: 'singleSelect', - valueFormatter: ({ value }) => - dataFormatter.permissionsManyListFormatter(value).join(', '), - renderEditCell: (params) => ( - - ), - }, - - { - field: 'organizations', - headerName: 'Organizations', - 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('organizations'), - valueGetter: (params: GridValueGetterParams) => - params?.value?.id ?? params?.value, - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ -
- -
, - ]; - }, - }, - ]; -}; diff --git a/frontend/src/helpers/dataFormatter.js b/frontend/src/helpers/dataFormatter.js index 9a199d7..b2e85f7 100644 --- a/frontend/src/helpers/dataFormatter.js +++ b/frontend/src/helpers/dataFormatter.js @@ -38,80 +38,4 @@ export default { } }); }, - - employeesManyListFormatter(val) { - if (!val || !val.length) return []; - return val.map((item) => item.first_name); - }, - employeesOneListFormatter(val) { - if (!val) return ''; - return val.first_name; - }, - employeesManyListFormatterEdit(val) { - if (!val || !val.length) return []; - return val.map((item) => { - return { id: item.id, label: item.first_name }; - }); - }, - employeesOneListFormatterEdit(val) { - if (!val) return ''; - return { label: val.first_name, id: val.id }; - }, - - rolesManyListFormatter(val) { - if (!val || !val.length) return []; - return val.map((item) => item.name); - }, - rolesOneListFormatter(val) { - if (!val) return ''; - return val.name; - }, - rolesManyListFormatterEdit(val) { - if (!val || !val.length) return []; - return val.map((item) => { - return { id: item.id, label: item.name }; - }); - }, - rolesOneListFormatterEdit(val) { - if (!val) return ''; - return { label: val.name, id: val.id }; - }, - - permissionsManyListFormatter(val) { - if (!val || !val.length) return []; - return val.map((item) => item.name); - }, - permissionsOneListFormatter(val) { - if (!val) return ''; - return val.name; - }, - permissionsManyListFormatterEdit(val) { - if (!val || !val.length) return []; - return val.map((item) => { - return { id: item.id, label: item.name }; - }); - }, - permissionsOneListFormatterEdit(val) { - if (!val) return ''; - return { label: val.name, id: val.id }; - }, - - organizationsManyListFormatter(val) { - if (!val || !val.length) return []; - return val.map((item) => item.name); - }, - organizationsOneListFormatter(val) { - if (!val) return ''; - return val.name; - }, - organizationsManyListFormatterEdit(val) { - if (!val || !val.length) return []; - return val.map((item) => { - return { id: item.id, label: item.name }; - }); - }, - organizationsOneListFormatterEdit(val) { - if (!val) return ''; - return { label: val.name, id: val.id }; - }, }; diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index 33aba96..7740edb 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -8,63 +8,6 @@ const menuAside: MenuAsideItem[] = [ label: 'Dashboard', }, - { - href: '/users/users-list', - label: 'Users', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiAccountGroup ?? icon.mdiTable, - permissions: 'READ_USERS', - }, - { - href: '/attendances/attendances-list', - label: 'Attendances', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: - 'mdiClock' in icon - ? icon['mdiClock' as keyof typeof icon] - : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_ATTENDANCES', - }, - { - href: '/employees/employees-list', - label: 'Employees', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: - 'mdiAccount' in icon - ? icon['mdiAccount' as keyof typeof icon] - : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_EMPLOYEES', - }, - { - href: '/payrolls/payrolls-list', - label: 'Payrolls', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: - 'mdiCurrencyInr' in icon - ? icon['mdiCurrencyInr' as keyof typeof icon] - : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_PAYROLLS', - }, - { - href: '/roles/roles-list', - label: 'Roles', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountVariantOutline ?? icon.mdiTable, - permissions: 'READ_ROLES', - }, - { - href: '/permissions/permissions-list', - label: 'Permissions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, - permissions: 'READ_PERMISSIONS', - }, { href: '/organizations/organizations-list', label: 'Organizations', diff --git a/frontend/src/pages/attendances/[attendancesId].tsx b/frontend/src/pages/attendances/[attendancesId].tsx deleted file mode 100644 index c195678..0000000 --- a/frontend/src/pages/attendances/[attendancesId].tsx +++ /dev/null @@ -1,226 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/attendances/attendancesSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditAttendances = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - time_in: new Date(), - - time_out: new Date(), - - total_hours: '', - - gps_location: '', - - selfie: [], - - employee: null, - - organizations: null, - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { attendances } = useAppSelector((state) => state.attendances); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { attendancesId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: attendancesId })); - }, [attendancesId]); - - useEffect(() => { - if (typeof attendances === 'object') { - setInitialValues(attendances); - } - }, [attendances]); - - useEffect(() => { - if (typeof attendances === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = attendances[el]), - ); - - setInitialValues(newInitialVal); - } - }, [attendances]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: attendancesId, data })); - await router.push('/attendances/attendances-list'); - }; - - return ( - <> - - {getPageTitle('Edit attendances')} - - - - {''} - - - handleSubmit(values)} - > -
- - - setInitialValues({ ...initialValues, time_in: date }) - } - /> - - - - - setInitialValues({ ...initialValues, time_out: date }) - } - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/attendances/attendances-list')} - /> - - -
-
-
- - ); -}; - -EditAttendances.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditAttendances; diff --git a/frontend/src/pages/attendances/attendances-edit.tsx b/frontend/src/pages/attendances/attendances-edit.tsx deleted file mode 100644 index 921e6ec..0000000 --- a/frontend/src/pages/attendances/attendances-edit.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/attendances/attendancesSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditAttendancesPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - time_in: new Date(), - - time_out: new Date(), - - total_hours: '', - - gps_location: '', - - selfie: [], - - employee: null, - - organizations: null, - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { attendances } = useAppSelector((state) => state.attendances); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof attendances === 'object') { - setInitialValues(attendances); - } - }, [attendances]); - - useEffect(() => { - if (typeof attendances === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = attendances[el]), - ); - setInitialValues(newInitialVal); - } - }, [attendances]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/attendances/attendances-list'); - }; - - return ( - <> - - {getPageTitle('Edit attendances')} - - - - {''} - - - handleSubmit(values)} - > -
- - - setInitialValues({ ...initialValues, time_in: date }) - } - /> - - - - - setInitialValues({ ...initialValues, time_out: date }) - } - /> - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/attendances/attendances-list')} - /> - - -
-
-
- - ); -}; - -EditAttendancesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditAttendancesPage; diff --git a/frontend/src/pages/attendances/attendances-list.tsx b/frontend/src/pages/attendances/attendances-list.tsx deleted file mode 100644 index 39a86b6..0000000 --- a/frontend/src/pages/attendances/attendances-list.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableAttendances from '../../components/Attendances/TableAttendances'; -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/attendances/attendancesSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const AttendancesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'GPSLocation', title: 'gps_location' }, - - { label: 'TotalHoursfortheDay', title: 'total_hours', number: 'true' }, - { label: 'TimeIn', title: 'time_in', date: 'true' }, - { label: 'TimeOut', title: 'time_out', date: 'true' }, - - { label: 'Employee', title: 'employee' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_ATTENDANCES'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAttendancesCSV = async () => { - const response = await axios({ - url: '/attendances?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 = 'attendancesCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Attendances')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
- -
- Switch to Table -
-
- - - - -
- - - - - ); -}; - -AttendancesTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default AttendancesTablesPage; diff --git a/frontend/src/pages/attendances/attendances-new.tsx b/frontend/src/pages/attendances/attendances-new.tsx deleted file mode 100644 index c1a39f0..0000000 --- a/frontend/src/pages/attendances/attendances-new.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { - mdiAccount, - mdiChartTimelineVariant, - mdiMail, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SwitchField } from '../../components/SwitchField'; - -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { RichTextField } from '../../components/RichTextField'; - -import { create } from '../../stores/attendances/attendancesSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - time_in: '', - - time_out: '', - - total_hours: '', - - gps_location: '', - - selfie: [], - - employee: '', - - organizations: '', -}; - -const AttendancesNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - // get from url params - const { dateRangeStart, dateRangeEnd } = router.query; - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/attendances/attendances-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/attendances/attendances-list')} - /> - - -
-
-
- - ); -}; - -AttendancesNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default AttendancesNew; diff --git a/frontend/src/pages/attendances/attendances-table.tsx b/frontend/src/pages/attendances/attendances-table.tsx deleted file mode 100644 index 9af62d8..0000000 --- a/frontend/src/pages/attendances/attendances-table.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableAttendances from '../../components/Attendances/TableAttendances'; -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/attendances/attendancesSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const AttendancesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'GPSLocation', title: 'gps_location' }, - - { label: 'TotalHoursfortheDay', title: 'total_hours', number: 'true' }, - { label: 'TimeIn', title: 'time_in', date: 'true' }, - { label: 'TimeOut', title: 'time_out', date: 'true' }, - - { label: 'Employee', title: 'employee' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_ATTENDANCES'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getAttendancesCSV = async () => { - const response = await axios({ - url: '/attendances?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 = 'attendancesCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Attendances')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
- - - Back to calendar - -
-
- - - -
- - - - - ); -}; - -AttendancesTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default AttendancesTablesPage; diff --git a/frontend/src/pages/attendances/attendances-view.tsx b/frontend/src/pages/attendances/attendances-view.tsx deleted file mode 100644 index 5b6be47..0000000 --- a/frontend/src/pages/attendances/attendances-view.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/attendances/attendancesSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const AttendancesView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { attendances } = useAppSelector((state) => state.attendances); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str, `str`); - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - return ( - <> - - {getPageTitle('View attendances')} - - - - - - - - {attendances.time_in ? ( - - ) : ( -

No TimeIn

- )} -
- - - {attendances.time_out ? ( - - ) : ( -

No TimeOut

- )} -
- -
-

TotalHoursfortheDay

-

{attendances?.total_hours || 'No data'}

-
- -
-

GPSLocation

-

{attendances?.gps_location}

-
- -
-

Selfie

- {attendances?.selfie?.length ? ( - - ) : ( -

No Selfie

- )} -
- -
-

Employee

- -

{attendances?.employee?.first_name ?? 'No data'}

-
- -
-

organizations

- -

{attendances?.organizations?.name ?? 'No data'}

-
- - - - router.push('/attendances/attendances-list')} - /> -
-
- - ); -}; - -AttendancesView.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default AttendancesView; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 14f3e90..e0d7485 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -28,12 +28,6 @@ const Dashboard = () => { defaultValue: 'Loading...', }); - const [users, setUsers] = React.useState(loadingMessage); - const [attendances, setAttendances] = React.useState(loadingMessage); - const [employees, setEmployees] = React.useState(loadingMessage); - const [payrolls, setPayrolls] = React.useState(loadingMessage); - const [roles, setRoles] = React.useState(loadingMessage); - const [permissions, setPermissions] = React.useState(loadingMessage); const [organizations, setOrganizations] = React.useState(loadingMessage); const [widgetsRole, setWidgetsRole] = React.useState({ @@ -47,24 +41,8 @@ const Dashboard = () => { const organizationId = currentUser?.organizations?.id; async function loadData() { - const entities = [ - 'users', - 'attendances', - 'employees', - 'payrolls', - 'roles', - 'permissions', - 'organizations', - ]; - const fns = [ - setUsers, - setAttendances, - setEmployees, - setPayrolls, - setRoles, - setPermissions, - setOrganizations, - ]; + const entities = ['organizations']; + const fns = [setOrganizations]; const requests = entities.map((entity, index) => { if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { @@ -176,212 +154,6 @@ const Dashboard = () => { id='dashboard' className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6' > - {hasPermission(currentUser, 'READ_USERS') && ( - -
-
-
-
- Users -
-
- {users} -
-
-
- -
-
-
- - )} - - {hasPermission(currentUser, 'READ_ATTENDANCES') && ( - -
-
-
-
- Attendances -
-
- {attendances} -
-
-
- -
-
-
- - )} - - {hasPermission(currentUser, 'READ_EMPLOYEES') && ( - -
-
-
-
- Employees -
-
- {employees} -
-
-
- -
-
-
- - )} - - {hasPermission(currentUser, 'READ_PAYROLLS') && ( - -
-
-
-
- Payrolls -
-
- {payrolls} -
-
-
- -
-
-
- - )} - - {hasPermission(currentUser, 'READ_ROLES') && ( - -
-
-
-
- Roles -
-
- {roles} -
-
-
- -
-
-
- - )} - - {hasPermission(currentUser, 'READ_PERMISSIONS') && ( - -
-
-
-
- Permissions -
-
- {permissions} -
-
-
- -
-
-
- - )} - {hasPermission(currentUser, 'READ_ORGANIZATIONS') && (
{

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

- <> -

Attendances Employee

- -
- - - - - - - - - - - - - - {employees.attendances_employee && - Array.isArray(employees.attendances_employee) && - employees.attendances_employee.map((item: any) => ( - - router.push( - `/attendances/attendances-view/?id=${item.id}`, - ) - } - > - - - - - - - - - ))} - -
TimeInTimeOutTotalHoursfortheDayGPSLocation
- {dataFormatter.dateTimeFormatter(item.time_in)} - - {dataFormatter.dateTimeFormatter(item.time_out)} - {item.total_hours}{item.gps_location}
-
- {!employees?.attendances_employee?.length && ( -
No data
- )} -
- - - <> -

Payrolls Employee

- -
- - - - - - - - {employees.payrolls_employee && - Array.isArray(employees.payrolls_employee) && - employees.payrolls_employee.map((item: any) => ( - - router.push( - `/payrolls/payrolls-view/?id=${item.id}`, - ) - } - > - - - ))} - -
TotalSalary
{item.total_salary}
-
- {!employees?.payrolls_employee?.length && ( -
No data
- )} -
- - diff --git a/frontend/src/pages/organizations/organizations-view.tsx b/frontend/src/pages/organizations/organizations-view.tsx index f856295..8ce7df9 100644 --- a/frontend/src/pages/organizations/organizations-view.tsx +++ b/frontend/src/pages/organizations/organizations-view.tsx @@ -63,587 +63,6 @@ const OrganizationsView = () => {

{organizations?.name}

- <> -

Users Organizations

- -
- - - - - - - - - - - - - - - - {organizations.users_organizations && - Array.isArray(organizations.users_organizations) && - organizations.users_organizations.map((item: any) => ( - - router.push(`/users/users-view/?id=${item.id}`) - } - > - - - - - - - - - - - ))} - -
First NameLast NamePhone NumberE-MailDisabled
{item.firstName}{item.lastName}{item.phoneNumber}{item.email} - {dataFormatter.booleanFormatter(item.disabled)} -
-
- {!organizations?.users_organizations?.length && ( -
No data
- )} -
- - - <> -

Attendances organizations

- -
- - - - - - - - - - - - - - {organizations.attendances_organizations && - Array.isArray(organizations.attendances_organizations) && - organizations.attendances_organizations.map( - (item: any) => ( - - router.push( - `/attendances/attendances-view/?id=${item.id}`, - ) - } - > - - - - - - - - - ), - )} - -
TimeInTimeOutTotalHoursfortheDayGPSLocation
- {dataFormatter.dateTimeFormatter(item.time_in)} - - {dataFormatter.dateTimeFormatter(item.time_out)} - {item.total_hours} - {item.gps_location} -
-
- {!organizations?.attendances_organizations?.length && ( -
No data
- )} -
- - - <> -

Employees Organization

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {organizations.employees_organization && - Array.isArray(organizations.employees_organization) && - organizations.employees_organization.map((item: any) => ( - - router.push( - `/employees/employees-view/?id=${item.id}`, - ) - } - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
TitleFirstNameMiddleNameLastNameDateofBirthSexAadharNumberPANNumberCellPhoneNumberCurrentResidentialAddressPermanentResidentialAddressDesignationDepartmentReportingManagerFlexibleWorkingHoursOfficeStartTimeOfficeEndTimeBasicSalaryHRAAllowance1Allowance2Allowance3AdvancePaid1AdvancePaid2EmployerPFContributionEmployeePFContributionProfessionalTaxAdvances1Advances2Deduction1Deduction2AllowedLeavesinaYearLeavesApplicableFromDateWeeklyOffEmployeeType
{item.title}{item.first_name}{item.middle_name}{item.last_name} - {dataFormatter.dateTimeFormatter( - item.date_of_birth, - )} - {item.sex} - {item.aadhar_number} - {item.pan_number} - {item.cell_phone_number} - - {item.current_residential_address} - - {item.permanent_residential_address} - {item.designation}{item.department} - {item.reporting_manager} - - {dataFormatter.booleanFormatter( - item.flexible_working_hours, - )} - - {dataFormatter.dateTimeFormatter( - item.office_start_time, - )} - - {dataFormatter.dateTimeFormatter( - item.office_end_time, - )} - {item.basic_salary}{item.hra}{item.allowance_1}{item.allowance_2}{item.allowance_3} - {item.advance_paid_1} - - {item.advance_paid_2} - - {item.employer_pf_contribution} - - {item.employee_pf_contribution} - - {item.professional_tax} - {item.advances_1}{item.advances_2}{item.deduction_1}{item.deduction_2} - {item.allowed_leaves} - - {dataFormatter.dateTimeFormatter( - item.leaves_applicable_from, - )} - {item.weekly_off} - {item.employee_type} -
-
- {!organizations?.employees_organization?.length && ( -
No data
- )} -
- - - <> -

Employees organizations

- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {organizations.employees_organizations && - Array.isArray(organizations.employees_organizations) && - organizations.employees_organizations.map((item: any) => ( - - router.push( - `/employees/employees-view/?id=${item.id}`, - ) - } - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
TitleFirstNameMiddleNameLastNameDateofBirthSexAadharNumberPANNumberCellPhoneNumberCurrentResidentialAddressPermanentResidentialAddressDesignationDepartmentReportingManagerFlexibleWorkingHoursOfficeStartTimeOfficeEndTimeBasicSalaryHRAAllowance1Allowance2Allowance3AdvancePaid1AdvancePaid2EmployerPFContributionEmployeePFContributionProfessionalTaxAdvances1Advances2Deduction1Deduction2AllowedLeavesinaYearLeavesApplicableFromDateWeeklyOffEmployeeType
{item.title}{item.first_name}{item.middle_name}{item.last_name} - {dataFormatter.dateTimeFormatter( - item.date_of_birth, - )} - {item.sex} - {item.aadhar_number} - {item.pan_number} - {item.cell_phone_number} - - {item.current_residential_address} - - {item.permanent_residential_address} - {item.designation}{item.department} - {item.reporting_manager} - - {dataFormatter.booleanFormatter( - item.flexible_working_hours, - )} - - {dataFormatter.dateTimeFormatter( - item.office_start_time, - )} - - {dataFormatter.dateTimeFormatter( - item.office_end_time, - )} - {item.basic_salary}{item.hra}{item.allowance_1}{item.allowance_2}{item.allowance_3} - {item.advance_paid_1} - - {item.advance_paid_2} - - {item.employer_pf_contribution} - - {item.employee_pf_contribution} - - {item.professional_tax} - {item.advances_1}{item.advances_2}{item.deduction_1}{item.deduction_2} - {item.allowed_leaves} - - {dataFormatter.dateTimeFormatter( - item.leaves_applicable_from, - )} - {item.weekly_off} - {item.employee_type} -
-
- {!organizations?.employees_organizations?.length && ( -
No data
- )} -
- - - <> -

Payrolls organizations

- -
- - - - - - - - {organizations.payrolls_organizations && - Array.isArray(organizations.payrolls_organizations) && - organizations.payrolls_organizations.map((item: any) => ( - - router.push( - `/payrolls/payrolls-view/?id=${item.id}`, - ) - } - > - - - ))} - -
TotalSalary
{item.total_salary}
-
- {!organizations?.payrolls_organizations?.length && ( -
No data
- )} -
- - { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - total_salary: '', - - employee: null, - - organizations: null, - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { payrolls } = useAppSelector((state) => state.payrolls); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { payrollsId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: payrollsId })); - }, [payrollsId]); - - useEffect(() => { - if (typeof payrolls === 'object') { - setInitialValues(payrolls); - } - }, [payrolls]); - - useEffect(() => { - if (typeof payrolls === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach((el) => (newInitialVal[el] = payrolls[el])); - - setInitialValues(newInitialVal); - } - }, [payrolls]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: payrollsId, data })); - await router.push('/payrolls/payrolls-list'); - }; - - return ( - <> - - {getPageTitle('Edit payrolls')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - router.push('/payrolls/payrolls-list')} - /> - - -
-
-
- - ); -}; - -EditPayrolls.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditPayrolls; diff --git a/frontend/src/pages/payrolls/payrolls-edit.tsx b/frontend/src/pages/payrolls/payrolls-edit.tsx deleted file mode 100644 index 4f40e77..0000000 --- a/frontend/src/pages/payrolls/payrolls-edit.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/payrolls/payrollsSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditPayrollsPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - total_salary: '', - - employee: null, - - organizations: null, - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { payrolls } = useAppSelector((state) => state.payrolls); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof payrolls === 'object') { - setInitialValues(payrolls); - } - }, [payrolls]); - - useEffect(() => { - if (typeof payrolls === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach((el) => (newInitialVal[el] = payrolls[el])); - setInitialValues(newInitialVal); - } - }, [payrolls]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/payrolls/payrolls-list'); - }; - - return ( - <> - - {getPageTitle('Edit payrolls')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - router.push('/payrolls/payrolls-list')} - /> - - -
-
-
- - ); -}; - -EditPayrollsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditPayrollsPage; diff --git a/frontend/src/pages/payrolls/payrolls-list.tsx b/frontend/src/pages/payrolls/payrolls-list.tsx deleted file mode 100644 index f5f44ce..0000000 --- a/frontend/src/pages/payrolls/payrolls-list.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TablePayrolls from '../../components/Payrolls/TablePayrolls'; -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/payrolls/payrollsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const PayrollsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'TotalSalary', title: 'total_salary', number: 'true' }, - - { label: 'Employee', title: 'employee' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_PAYROLLS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getPayrollsCSV = async () => { - const response = await axios({ - url: '/payrolls?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 = 'payrollsCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Payrolls')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
- -
- Switch to Table -
-
- - - - -
- - - - - ); -}; - -PayrollsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PayrollsTablesPage; diff --git a/frontend/src/pages/payrolls/payrolls-new.tsx b/frontend/src/pages/payrolls/payrolls-new.tsx deleted file mode 100644 index 0da00e0..0000000 --- a/frontend/src/pages/payrolls/payrolls-new.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { - mdiAccount, - mdiChartTimelineVariant, - mdiMail, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SwitchField } from '../../components/SwitchField'; - -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { RichTextField } from '../../components/RichTextField'; - -import { create } from '../../stores/payrolls/payrollsSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - total_salary: '', - - employee: '', - - organizations: '', -}; - -const PayrollsNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/payrolls/payrolls-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - router.push('/payrolls/payrolls-list')} - /> - - -
-
-
- - ); -}; - -PayrollsNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PayrollsNew; diff --git a/frontend/src/pages/payrolls/payrolls-table.tsx b/frontend/src/pages/payrolls/payrolls-table.tsx deleted file mode 100644 index 6e1223b..0000000 --- a/frontend/src/pages/payrolls/payrolls-table.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TablePayrolls from '../../components/Payrolls/TablePayrolls'; -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/payrolls/payrollsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const PayrollsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'TotalSalary', title: 'total_salary', number: 'true' }, - - { label: 'Employee', title: 'employee' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_PAYROLLS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getPayrollsCSV = async () => { - const response = await axios({ - url: '/payrolls?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 = 'payrollsCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Payrolls')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
- - - Back to list - -
-
- - - -
- - - - - ); -}; - -PayrollsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PayrollsTablesPage; diff --git a/frontend/src/pages/payrolls/payrolls-view.tsx b/frontend/src/pages/payrolls/payrolls-view.tsx deleted file mode 100644 index d6e38d7..0000000 --- a/frontend/src/pages/payrolls/payrolls-view.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/payrolls/payrollsSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const PayrollsView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { payrolls } = useAppSelector((state) => state.payrolls); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str, `str`); - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - return ( - <> - - {getPageTitle('View payrolls')} - - - - - - -
-

TotalSalary

-

{payrolls?.total_salary || 'No data'}

-
- -
-

Employee

- -

{payrolls?.employee?.first_name ?? 'No data'}

-
- -
-

organizations

- -

{payrolls?.organizations?.name ?? 'No data'}

-
- - - - router.push('/payrolls/payrolls-list')} - /> -
-
- - ); -}; - -PayrollsView.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PayrollsView; diff --git a/frontend/src/pages/permissions/[permissionsId].tsx b/frontend/src/pages/permissions/[permissionsId].tsx deleted file mode 100644 index c49d722..0000000 --- a/frontend/src/pages/permissions/[permissionsId].tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/permissions/permissionsSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditPermissions = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - name: '', - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { permissions } = useAppSelector((state) => state.permissions); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { permissionsId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: permissionsId })); - }, [permissionsId]); - - useEffect(() => { - if (typeof permissions === 'object') { - setInitialValues(permissions); - } - }, [permissions]); - - useEffect(() => { - if (typeof permissions === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = permissions[el]), - ); - - setInitialValues(newInitialVal); - } - }, [permissions]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: permissionsId, data })); - await router.push('/permissions/permissions-list'); - }; - - return ( - <> - - {getPageTitle('Edit permissions')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - router.push('/permissions/permissions-list')} - /> - - -
-
-
- - ); -}; - -EditPermissions.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditPermissions; diff --git a/frontend/src/pages/permissions/permissions-edit.tsx b/frontend/src/pages/permissions/permissions-edit.tsx deleted file mode 100644 index c540bf7..0000000 --- a/frontend/src/pages/permissions/permissions-edit.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/permissions/permissionsSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditPermissionsPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - name: '', - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { permissions } = useAppSelector((state) => state.permissions); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof permissions === 'object') { - setInitialValues(permissions); - } - }, [permissions]); - - useEffect(() => { - if (typeof permissions === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = permissions[el]), - ); - setInitialValues(newInitialVal); - } - }, [permissions]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/permissions/permissions-list'); - }; - - return ( - <> - - {getPageTitle('Edit permissions')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - router.push('/permissions/permissions-list')} - /> - - -
-
-
- - ); -}; - -EditPermissionsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditPermissionsPage; diff --git a/frontend/src/pages/permissions/permissions-list.tsx b/frontend/src/pages/permissions/permissions-list.tsx deleted file mode 100644 index 97955e4..0000000 --- a/frontend/src/pages/permissions/permissions-list.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TablePermissions from '../../components/Permissions/TablePermissions'; -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/permissions/permissionsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const PermissionsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([{ label: 'Name', title: 'name' }]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_PERMISSIONS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getPermissionsCSV = async () => { - const response = await axios({ - url: '/permissions?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 = 'permissionsCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Permissions')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - - -
- - - - - ); -}; - -PermissionsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PermissionsTablesPage; diff --git a/frontend/src/pages/permissions/permissions-new.tsx b/frontend/src/pages/permissions/permissions-new.tsx deleted file mode 100644 index e5a9eb0..0000000 --- a/frontend/src/pages/permissions/permissions-new.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { - mdiAccount, - mdiChartTimelineVariant, - mdiMail, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SwitchField } from '../../components/SwitchField'; - -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { RichTextField } from '../../components/RichTextField'; - -import { create } from '../../stores/permissions/permissionsSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - name: '', -}; - -const PermissionsNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/permissions/permissions-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - router.push('/permissions/permissions-list')} - /> - - -
-
-
- - ); -}; - -PermissionsNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PermissionsNew; diff --git a/frontend/src/pages/permissions/permissions-table.tsx b/frontend/src/pages/permissions/permissions-table.tsx deleted file mode 100644 index 4f54815..0000000 --- a/frontend/src/pages/permissions/permissions-table.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TablePermissions from '../../components/Permissions/TablePermissions'; -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/permissions/permissionsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const PermissionsTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([{ label: 'Name', title: 'name' }]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_PERMISSIONS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getPermissionsCSV = async () => { - const response = await axios({ - url: '/permissions?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 = 'permissionsCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Permissions')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - -
- - - - - ); -}; - -PermissionsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PermissionsTablesPage; diff --git a/frontend/src/pages/permissions/permissions-view.tsx b/frontend/src/pages/permissions/permissions-view.tsx deleted file mode 100644 index aae7c5e..0000000 --- a/frontend/src/pages/permissions/permissions-view.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/permissions/permissionsSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const PermissionsView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { permissions } = useAppSelector((state) => state.permissions); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str, `str`); - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - return ( - <> - - {getPageTitle('View permissions')} - - - - - - -
-

Name

-

{permissions?.name}

-
- - - - router.push('/permissions/permissions-list')} - /> -
-
- - ); -}; - -PermissionsView.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default PermissionsView; diff --git a/frontend/src/pages/roles/[rolesId].tsx b/frontend/src/pages/roles/[rolesId].tsx deleted file mode 100644 index d1bbf63..0000000 --- a/frontend/src/pages/roles/[rolesId].tsx +++ /dev/null @@ -1,151 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/roles/rolesSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditRoles = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - name: '', - - permissions: [], - - globalAccess: false, - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { roles } = useAppSelector((state) => state.roles); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { rolesId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: rolesId })); - }, [rolesId]); - - useEffect(() => { - if (typeof roles === 'object') { - setInitialValues(roles); - } - }, [roles]); - - useEffect(() => { - if (typeof roles === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach((el) => (newInitialVal[el] = roles[el])); - - setInitialValues(newInitialVal); - } - }, [roles]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: rolesId, data })); - await router.push('/roles/roles-list'); - }; - - return ( - <> - - {getPageTitle('Edit roles')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - router.push('/roles/roles-list')} - /> - - -
-
-
- - ); -}; - -EditRoles.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditRoles; diff --git a/frontend/src/pages/roles/roles-edit.tsx b/frontend/src/pages/roles/roles-edit.tsx deleted file mode 100644 index 618129e..0000000 --- a/frontend/src/pages/roles/roles-edit.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/roles/rolesSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditRolesPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - name: '', - - permissions: [], - - globalAccess: false, - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { roles } = useAppSelector((state) => state.roles); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof roles === 'object') { - setInitialValues(roles); - } - }, [roles]); - - useEffect(() => { - if (typeof roles === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach((el) => (newInitialVal[el] = roles[el])); - setInitialValues(newInitialVal); - } - }, [roles]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/roles/roles-list'); - }; - - return ( - <> - - {getPageTitle('Edit roles')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - router.push('/roles/roles-list')} - /> - - -
-
-
- - ); -}; - -EditRolesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditRolesPage; diff --git a/frontend/src/pages/roles/roles-list.tsx b/frontend/src/pages/roles/roles-list.tsx deleted file mode 100644 index 5c07566..0000000 --- a/frontend/src/pages/roles/roles-list.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableRoles from '../../components/Roles/TableRoles'; -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/roles/rolesSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const RolesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'Name', title: 'name' }, - - { label: 'Permissions', title: 'permissions' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_ROLES'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getRolesCSV = async () => { - const response = await axios({ - url: '/roles?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 = 'rolesCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Roles')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - - -
- - - - - ); -}; - -RolesTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - {page} - ); -}; - -export default RolesTablesPage; diff --git a/frontend/src/pages/roles/roles-new.tsx b/frontend/src/pages/roles/roles-new.tsx deleted file mode 100644 index 18f111f..0000000 --- a/frontend/src/pages/roles/roles-new.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { - mdiAccount, - mdiChartTimelineVariant, - mdiMail, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SwitchField } from '../../components/SwitchField'; - -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { RichTextField } from '../../components/RichTextField'; - -import { create } from '../../stores/roles/rolesSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - name: '', - - permissions: [], - - globalAccess: false, -}; - -const RolesNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/roles/roles-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - router.push('/roles/roles-list')} - /> - - -
-
-
- - ); -}; - -RolesNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default RolesNew; diff --git a/frontend/src/pages/roles/roles-table.tsx b/frontend/src/pages/roles/roles-table.tsx deleted file mode 100644 index 380cca9..0000000 --- a/frontend/src/pages/roles/roles-table.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableRoles from '../../components/Roles/TableRoles'; -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/roles/rolesSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const RolesTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'Name', title: 'name' }, - - { label: 'Permissions', title: 'permissions' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_ROLES'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getRolesCSV = async () => { - const response = await axios({ - url: '/roles?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 = 'rolesCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Roles')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - -
- - - - - ); -}; - -RolesTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - {page} - ); -}; - -export default RolesTablesPage; diff --git a/frontend/src/pages/roles/roles-view.tsx b/frontend/src/pages/roles/roles-view.tsx deleted file mode 100644 index 66b5dda..0000000 --- a/frontend/src/pages/roles/roles-view.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/roles/rolesSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const RolesView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { roles } = useAppSelector((state) => state.roles); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str, `str`); - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - return ( - <> - - {getPageTitle('View roles')} - - - - - - -
-

Name

-

{roles?.name}

-
- - <> -

Permissions

- -
- - - - - - - - {roles.permissions && - Array.isArray(roles.permissions) && - roles.permissions.map((item: any) => ( - - router.push( - `/permissions/permissions-view/?id=${item.id}`, - ) - } - > - - - ))} - -
Name
{item.name}
-
- {!roles?.permissions?.length && ( -
No data
- )} -
- - - - null }} - disabled - /> - - - <> -

Users App Role

- -
- - - - - - - - - - - - - - - - {roles.users_app_role && - Array.isArray(roles.users_app_role) && - roles.users_app_role.map((item: any) => ( - - router.push(`/users/users-view/?id=${item.id}`) - } - > - - - - - - - - - - - ))} - -
First NameLast NamePhone NumberE-MailDisabled
{item.firstName}{item.lastName}{item.phoneNumber}{item.email} - {dataFormatter.booleanFormatter(item.disabled)} -
-
- {!roles?.users_app_role?.length && ( -
No data
- )} -
- - - - - router.push('/roles/roles-list')} - /> -
-
- - ); -}; - -RolesView.getLayout = function getLayout(page: ReactElement) { - return ( - {page} - ); -}; - -export default RolesView; diff --git a/frontend/src/pages/users/[usersId].tsx b/frontend/src/pages/users/[usersId].tsx deleted file mode 100644 index 965dcf6..0000000 --- a/frontend/src/pages/users/[usersId].tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/users/usersSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditUsers = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - firstName: '', - - lastName: '', - - phoneNumber: '', - - email: '', - - disabled: false, - - avatar: [], - - app_role: null, - - custom_permissions: [], - - organizations: null, - - password: '', - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { users } = useAppSelector((state) => state.users); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { usersId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: usersId })); - }, [usersId]); - - useEffect(() => { - if (typeof users === 'object') { - setInitialValues(users); - } - }, [users]); - - useEffect(() => { - if (typeof users === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach((el) => (newInitialVal[el] = users[el])); - - setInitialValues(newInitialVal); - } - }, [users]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: usersId, data })); - await router.push('/users/users-list'); - }; - - return ( - <> - - {getPageTitle('Edit users')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/users/users-list')} - /> - - -
-
-
- - ); -}; - -EditUsers.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditUsers; diff --git a/frontend/src/pages/users/users-edit.tsx b/frontend/src/pages/users/users-edit.tsx deleted file mode 100644 index c81e94d..0000000 --- a/frontend/src/pages/users/users-edit.tsx +++ /dev/null @@ -1,220 +0,0 @@ -import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; - -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { SwitchField } from '../../components/SwitchField'; -import { RichTextField } from '../../components/RichTextField'; - -import { update, fetch } from '../../stores/users/usersSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const EditUsersPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - firstName: '', - - lastName: '', - - phoneNumber: '', - - email: '', - - disabled: false, - - avatar: [], - - app_role: null, - - custom_permissions: [], - - organizations: null, - - password: '', - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { users } = useAppSelector((state) => state.users); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof users === 'object') { - setInitialValues(users); - } - }, [users]); - - useEffect(() => { - if (typeof users === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach((el) => (newInitialVal[el] = users[el])); - setInitialValues(newInitialVal); - } - }, [users]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/users/users-list'); - }; - - return ( - <> - - {getPageTitle('Edit users')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/users/users-list')} - /> - - -
-
-
- - ); -}; - -EditUsersPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditUsersPage; diff --git a/frontend/src/pages/users/users-list.tsx b/frontend/src/pages/users/users-list.tsx deleted file mode 100644 index 6d03ebc..0000000 --- a/frontend/src/pages/users/users-list.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableUsers from '../../components/Users/TableUsers'; -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/users/usersSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const UsersTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'First Name', title: 'firstName' }, - { label: 'Last Name', title: 'lastName' }, - { label: 'Phone Number', title: 'phoneNumber' }, - { label: 'E-Mail', title: 'email' }, - - { label: 'App Role', title: 'app_role' }, - - { label: 'Custom Permissions', title: 'custom_permissions' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_USERS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getUsersCSV = async () => { - const response = await axios({ - url: '/users?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 = 'usersCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Users')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - - -
- - - - - ); -}; - -UsersTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - {page} - ); -}; - -export default UsersTablesPage; diff --git a/frontend/src/pages/users/users-new.tsx b/frontend/src/pages/users/users-new.tsx deleted file mode 100644 index 8c25d83..0000000 --- a/frontend/src/pages/users/users-new.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import { - mdiAccount, - mdiChartTimelineVariant, - mdiMail, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; - -import { Field, Form, Formik } from 'formik'; -import FormField from '../../components/FormField'; -import BaseDivider from '../../components/BaseDivider'; -import BaseButtons from '../../components/BaseButtons'; -import BaseButton from '../../components/BaseButton'; -import FormCheckRadio from '../../components/FormCheckRadio'; -import FormCheckRadioGroup from '../../components/FormCheckRadioGroup'; -import FormFilePicker from '../../components/FormFilePicker'; -import FormImagePicker from '../../components/FormImagePicker'; -import { SwitchField } from '../../components/SwitchField'; - -import { SelectField } from '../../components/SelectField'; -import { SelectFieldMany } from '../../components/SelectFieldMany'; -import { RichTextField } from '../../components/RichTextField'; - -import { create } from '../../stores/users/usersSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - firstName: '', - - lastName: '', - - phoneNumber: '', - - email: '', - - disabled: false, - - avatar: [], - - app_role: '', - - custom_permissions: [], - - organizations: '', -}; - -const UsersNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/users/users-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/users/users-list')} - /> - - -
-
-
- - ); -}; - -UsersNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default UsersNew; diff --git a/frontend/src/pages/users/users-table.tsx b/frontend/src/pages/users/users-table.tsx deleted file mode 100644 index a408cd6..0000000 --- a/frontend/src/pages/users/users-table.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { mdiChartTimelineVariant } from '@mdi/js'; -import Head from 'next/head'; -import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react'; -import CardBox from '../../components/CardBox'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import SectionMain from '../../components/SectionMain'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../../config'; -import TableUsers from '../../components/Users/TableUsers'; -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/users/usersSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const UsersTablesPage = () => { - const [filterItems, setFilterItems] = useState([]); - const [csvFile, setCsvFile] = useState(null); - const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([ - { label: 'First Name', title: 'firstName' }, - { label: 'Last Name', title: 'lastName' }, - { label: 'Phone Number', title: 'phoneNumber' }, - { label: 'E-Mail', title: 'email' }, - - { label: 'App Role', title: 'app_role' }, - - { label: 'Custom Permissions', title: 'custom_permissions' }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_USERS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getUsersCSV = async () => { - const response = await axios({ - url: '/users?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 = 'usersCSV.csv'; - link.click(); - }; - - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; - - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; - - return ( - <> - - {getPageTitle('Users')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - -
- - - - - ); -}; - -UsersTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - {page} - ); -}; - -export default UsersTablesPage; diff --git a/frontend/src/pages/users/users-view.tsx b/frontend/src/pages/users/users-view.tsx deleted file mode 100644 index f897235..0000000 --- a/frontend/src/pages/users/users-view.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import React, { ReactElement, useEffect } from 'react'; -import Head from 'next/head'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { fetch } from '../../stores/users/usersSlice'; -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'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const UsersView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { users } = useAppSelector((state) => state.users); - - const { currentUser } = useAppSelector((state) => state.auth); - - const { id } = router.query; - - function removeLastCharacter(str) { - console.log(str, `str`); - return str.slice(0, -1); - } - - useEffect(() => { - dispatch(fetch({ id })); - }, [dispatch, id]); - - return ( - <> - - {getPageTitle('View users')} - - - - - - -
-

First Name

-

{users?.firstName}

-
- -
-

Last Name

-

{users?.lastName}

-
- -
-

Phone Number

-

{users?.phoneNumber}

-
- -
-

E-Mail

-

{users?.email}

-
- - - null }} - disabled - /> - - -
-

Avatar

- {users?.avatar?.length ? ( - - ) : ( -

No Avatar

- )} -
- -
-

App Role

- -

{users?.app_role?.name ?? 'No data'}

-
- - <> -

Custom Permissions

- -
- - - - - - - - {users.custom_permissions && - Array.isArray(users.custom_permissions) && - users.custom_permissions.map((item: any) => ( - - router.push( - `/permissions/permissions-view/?id=${item.id}`, - ) - } - > - - - ))} - -
Name
{item.name}
-
- {!users?.custom_permissions?.length && ( -
No data
- )} -
- - -
-

Organizations

- -

{users?.organizations?.name ?? 'No data'}

-
- - - - router.push('/users/users-list')} - /> -
-
- - ); -}; - -UsersView.getLayout = function getLayout(page: ReactElement) { - return ( - {page} - ); -}; - -export default UsersView; diff --git a/frontend/src/pages/web_pages/home.tsx b/frontend/src/pages/web_pages/home.tsx index 6509e55..b077a12 100644 --- a/frontend/src/pages/web_pages/home.tsx +++ b/frontend/src/pages/web_pages/home.tsx @@ -112,7 +112,7 @@ export default function WebSite() { diff --git a/frontend/src/pages/web_pages/pricing.tsx b/frontend/src/pages/web_pages/pricing.tsx index e9a0506..e2a4e96 100644 --- a/frontend/src/pages/web_pages/pricing.tsx +++ b/frontend/src/pages/web_pages/pricing.tsx @@ -81,7 +81,7 @@ export default function WebSite() { diff --git a/frontend/src/pages/web_pages/services.tsx b/frontend/src/pages/web_pages/services.tsx index 4285a35..14a6108 100644 --- a/frontend/src/pages/web_pages/services.tsx +++ b/frontend/src/pages/web_pages/services.tsx @@ -115,7 +115,7 @@ export default function WebSite() { diff --git a/frontend/src/stores/attendances/attendancesSlice.ts b/frontend/src/stores/attendances/attendancesSlice.ts deleted file mode 100644 index 80978b2..0000000 --- a/frontend/src/stores/attendances/attendancesSlice.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; -import axios from 'axios'; -import { - fulfilledNotify, - rejectNotify, - resetNotify, -} from '../../helpers/notifyStateHandler'; - -interface MainState { - attendances: any; - loading: boolean; - count: number; - refetch: boolean; - rolesWidgets: any[]; - notify: { - showNotification: boolean; - textNotification: string; - typeNotification: string; - }; -} - -const initialState: MainState = { - attendances: [], - loading: false, - count: 0, - refetch: false, - rolesWidgets: [], - notify: { - showNotification: false, - textNotification: '', - typeNotification: 'warn', - }, -}; - -export const fetch = createAsyncThunk( - 'attendances/fetch', - async (data: any) => { - const { id, query } = data; - const result = await axios.get( - `attendances${query || (id ? `/${id}` : '')}`, - ); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; - }, -); - -export const deleteItemsByIds = createAsyncThunk( - 'attendances/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('attendances/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'attendances/deleteAttendances', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`attendances/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'attendances/createAttendances', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('attendances', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const uploadCsv = createAsyncThunk( - 'attendances/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('attendances/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'attendances/updateAttendances', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`attendances/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const attendancesSlice = createSlice({ - name: 'attendances', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.attendances = action.payload.rows; - state.count = action.payload.count; - } else { - state.attendances = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Attendances has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Attendances'.slice(0, -1)} has been deleted`); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Attendances'.slice(0, -1)} has been created`); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Attendances'.slice(0, -1)} has been updated`); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Attendances has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = attendancesSlice.actions; - -export default attendancesSlice.reducer; diff --git a/frontend/src/stores/payrolls/payrollsSlice.ts b/frontend/src/stores/payrolls/payrollsSlice.ts deleted file mode 100644 index bbb5d5d..0000000 --- a/frontend/src/stores/payrolls/payrollsSlice.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; -import axios from 'axios'; -import { - fulfilledNotify, - rejectNotify, - resetNotify, -} from '../../helpers/notifyStateHandler'; - -interface MainState { - payrolls: any; - loading: boolean; - count: number; - refetch: boolean; - rolesWidgets: any[]; - notify: { - showNotification: boolean; - textNotification: string; - typeNotification: string; - }; -} - -const initialState: MainState = { - payrolls: [], - loading: false, - count: 0, - refetch: false, - rolesWidgets: [], - notify: { - showNotification: false, - textNotification: '', - typeNotification: 'warn', - }, -}; - -export const fetch = createAsyncThunk('payrolls/fetch', async (data: any) => { - const { id, query } = data; - const result = await axios.get(`payrolls${query || (id ? `/${id}` : '')}`); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; -}); - -export const deleteItemsByIds = createAsyncThunk( - 'payrolls/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('payrolls/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'payrolls/deletePayrolls', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`payrolls/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'payrolls/createPayrolls', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('payrolls', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const uploadCsv = createAsyncThunk( - 'payrolls/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('payrolls/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'payrolls/updatePayrolls', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`payrolls/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const payrollsSlice = createSlice({ - name: 'payrolls', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.payrolls = action.payload.rows; - state.count = action.payload.count; - } else { - state.payrolls = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Payrolls has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Payrolls'.slice(0, -1)} has been deleted`); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Payrolls'.slice(0, -1)} has been created`); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Payrolls'.slice(0, -1)} has been updated`); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Payrolls has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = payrollsSlice.actions; - -export default payrollsSlice.reducer; diff --git a/frontend/src/stores/permissions/permissionsSlice.ts b/frontend/src/stores/permissions/permissionsSlice.ts deleted file mode 100644 index 11b8224..0000000 --- a/frontend/src/stores/permissions/permissionsSlice.ts +++ /dev/null @@ -1,241 +0,0 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; -import axios from 'axios'; -import { - fulfilledNotify, - rejectNotify, - resetNotify, -} from '../../helpers/notifyStateHandler'; - -interface MainState { - permissions: any; - loading: boolean; - count: number; - refetch: boolean; - rolesWidgets: any[]; - notify: { - showNotification: boolean; - textNotification: string; - typeNotification: string; - }; -} - -const initialState: MainState = { - permissions: [], - loading: false, - count: 0, - refetch: false, - rolesWidgets: [], - notify: { - showNotification: false, - textNotification: '', - typeNotification: 'warn', - }, -}; - -export const fetch = createAsyncThunk( - 'permissions/fetch', - async (data: any) => { - const { id, query } = data; - const result = await axios.get( - `permissions${query || (id ? `/${id}` : '')}`, - ); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; - }, -); - -export const deleteItemsByIds = createAsyncThunk( - 'permissions/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('permissions/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'permissions/deletePermissions', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`permissions/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'permissions/createPermissions', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('permissions', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const uploadCsv = createAsyncThunk( - 'permissions/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('permissions/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'permissions/updatePermissions', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`permissions/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const permissionsSlice = createSlice({ - name: 'permissions', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.permissions = action.payload.rows; - state.count = action.payload.count; - } else { - state.permissions = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Permissions has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Permissions'.slice(0, -1)} has been deleted`); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Permissions'.slice(0, -1)} has been created`); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Permissions'.slice(0, -1)} has been updated`); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Permissions has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = permissionsSlice.actions; - -export default permissionsSlice.reducer; diff --git a/frontend/src/stores/roles/rolesSlice.ts b/frontend/src/stores/roles/rolesSlice.ts deleted file mode 100644 index 177e83b..0000000 --- a/frontend/src/stores/roles/rolesSlice.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; -import axios from 'axios'; -import { - fulfilledNotify, - rejectNotify, - resetNotify, -} from '../../helpers/notifyStateHandler'; - -interface MainState { - roles: any; - loading: boolean; - count: number; - refetch: boolean; - rolesWidgets: any[]; - notify: { - showNotification: boolean; - textNotification: string; - typeNotification: string; - }; -} - -const initialState: MainState = { - roles: [], - loading: false, - count: 0, - refetch: false, - rolesWidgets: [], - notify: { - showNotification: false, - textNotification: '', - typeNotification: 'warn', - }, -}; - -export const fetch = createAsyncThunk('roles/fetch', async (data: any) => { - const { id, query } = data; - const result = await axios.get(`roles${query || (id ? `/${id}` : '')}`); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; -}); - -export const deleteItemsByIds = createAsyncThunk( - 'roles/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('roles/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'roles/deleteRoles', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`roles/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'roles/createRoles', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('roles', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const uploadCsv = createAsyncThunk( - 'roles/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('roles/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'roles/updateRoles', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`roles/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const removeWidget = createAsyncThunk( - 'openai/removeWidget', - async (payload: any) => { - const result = await axios.delete(`openai/roles-info/${payload.id}`, { - params: { - roleId: payload.roleId, - infoId: payload.widgetId, - key: 'widgets', - }, - }); - return result.data; - }, -); - -export const fetchWidgets = createAsyncThunk( - 'openai/fetchWidgets', - async (roleId: any) => { - const result = await axios.get( - `openai/info-by-key?key=widgets&roleId=${roleId}`, - ); - return result.data; - }, -); - -export const rolesSlice = createSlice({ - name: 'roles', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.roles = action.payload.rows; - state.count = action.payload.count; - } else { - state.roles = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Roles has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Roles'.slice(0, -1)} has been deleted`); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Roles'.slice(0, -1)} has been created`); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Roles'.slice(0, -1)} has been updated`); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Roles has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(removeWidget.pending, (state) => { - state.loading = true; - }); - builder.addCase(removeWidget.fulfilled, (state) => { - state.loading = false; - }); - builder.addCase(removeWidget.rejected, (state) => { - state.loading = false; - }); - - builder.addCase(fetchWidgets.pending, (state) => { - state.loading = true; - state.rolesWidgets = []; - }); - builder.addCase(fetchWidgets.fulfilled, (state, action) => { - state.loading = false; - state.rolesWidgets = action.payload; - }); - builder.addCase(fetchWidgets.rejected, (state) => { - state.loading = false; - state.rolesWidgets = []; - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = rolesSlice.actions; - -export default rolesSlice.reducer; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index 8ba91e1..f898f0e 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -4,12 +4,6 @@ import mainReducer from './mainSlice'; import authSlice from './authSlice'; import openAiSlice from './openAiSlice'; -import usersSlice from './users/usersSlice'; -import attendancesSlice from './attendances/attendancesSlice'; -import employeesSlice from './employees/employeesSlice'; -import payrollsSlice from './payrolls/payrollsSlice'; -import rolesSlice from './roles/rolesSlice'; -import permissionsSlice from './permissions/permissionsSlice'; import organizationsSlice from './organizations/organizationsSlice'; export const store = configureStore({ @@ -19,12 +13,6 @@ export const store = configureStore({ auth: authSlice, openAi: openAiSlice, - users: usersSlice, - attendances: attendancesSlice, - employees: employeesSlice, - payrolls: payrollsSlice, - roles: rolesSlice, - permissions: permissionsSlice, organizations: organizationsSlice, }, }); diff --git a/frontend/src/stores/users/usersSlice.ts b/frontend/src/stores/users/usersSlice.ts deleted file mode 100644 index f182315..0000000 --- a/frontend/src/stores/users/usersSlice.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; -import axios from 'axios'; -import { - fulfilledNotify, - rejectNotify, - resetNotify, -} from '../../helpers/notifyStateHandler'; - -interface MainState { - users: any; - loading: boolean; - count: number; - refetch: boolean; - rolesWidgets: any[]; - notify: { - showNotification: boolean; - textNotification: string; - typeNotification: string; - }; -} - -const initialState: MainState = { - users: [], - loading: false, - count: 0, - refetch: false, - rolesWidgets: [], - notify: { - showNotification: false, - textNotification: '', - typeNotification: 'warn', - }, -}; - -export const fetch = createAsyncThunk('users/fetch', async (data: any) => { - const { id, query } = data; - const result = await axios.get(`users${query || (id ? `/${id}` : '')}`); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; -}); - -export const deleteItemsByIds = createAsyncThunk( - 'users/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('users/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'users/deleteUsers', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`users/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'users/createUsers', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('users', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const uploadCsv = createAsyncThunk( - 'users/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('users/bulk-import', data, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const update = createAsyncThunk( - 'users/updateUsers', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`users/${payload.id}`, { - id: payload.id, - data: payload.data, - }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const usersSlice = createSlice({ - name: 'users', - initialState, - reducers: { - setRefetch: (state, action: PayloadAction) => { - state.refetch = action.payload; - }, - }, - extraReducers: (builder) => { - builder.addCase(fetch.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(fetch.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(fetch.fulfilled, (state, action) => { - if (action.payload.rows && action.payload.count >= 0) { - state.users = action.payload.rows; - state.count = action.payload.count; - } else { - state.users = action.payload; - } - state.loading = false; - }); - - builder.addCase(deleteItemsByIds.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItemsByIds.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Users has been deleted'); - }); - - builder.addCase(deleteItemsByIds.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(deleteItem.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - - builder.addCase(deleteItem.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Users'.slice(0, -1)} has been deleted`); - }); - - builder.addCase(deleteItem.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(create.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(create.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Users'.slice(0, -1)} has been created`); - }); - - builder.addCase(update.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(update.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, `${'Users'.slice(0, -1)} has been updated`); - }); - builder.addCase(update.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - - builder.addCase(uploadCsv.pending, (state) => { - state.loading = true; - resetNotify(state); - }); - builder.addCase(uploadCsv.fulfilled, (state) => { - state.loading = false; - fulfilledNotify(state, 'Users has been uploaded'); - }); - builder.addCase(uploadCsv.rejected, (state, action) => { - state.loading = false; - rejectNotify(state, action); - }); - }, -}); - -// Action creators are generated for each case reducer function -export const { setRefetch } = usersSlice.actions; - -export default usersSlice.reducer;