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 c4509a2..93bda4c 100644 --- a/app-shell/src/_schema.json +++ b/app-shell/src/_schema.json @@ -1,5 +1,4 @@ - - { - "Initial version": "{\"iv\":\"vPv/E309+fn9Mbil\",\"encryptedData\":\"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\"}" -} + "Initial version": "{\"iv\":\"vPv/E309+fn9Mbil\",\"encryptedData\":\"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\"}", + "": "{\"iv\":\"emmufBc8wRQoTo8m\",\"encryptedData\":\"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\"}" +} \ No newline at end of file diff --git a/backend/src/db/api/conversions.js b/backend/src/db/api/conversions.js deleted file mode 100644 index e6d40e3..0000000 --- a/backend/src/db/api/conversions.js +++ /dev/null @@ -1,457 +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 ConversionsDBApi { - static async create(data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversions = await db.conversions.create( - { - id: data.id || undefined, - - input_value: data.input_value || null, - input_unit: data.input_unit || null, - output_value_btu_h: data.output_value_btu_h || null, - output_value_kw: data.output_value_kw || null, - output_value_kcal_h: data.output_value_kcal_h || null, - output_value_tr: data.output_value_tr || null, - output_value_hp: data.output_value_hp || null, - output_value_w: data.output_value_w || null, - importHash: data.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - }, - { transaction }, - ); - - return conversions; - } - - 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 conversionsData = data.map((item, index) => ({ - id: item.id || undefined, - - input_value: item.input_value || null, - input_unit: item.input_unit || null, - output_value_btu_h: item.output_value_btu_h || null, - output_value_kw: item.output_value_kw || null, - output_value_kcal_h: item.output_value_kcal_h || null, - output_value_tr: item.output_value_tr || null, - output_value_hp: item.output_value_hp || null, - output_value_w: item.output_value_w || null, - importHash: item.importHash || null, - createdById: currentUser.id, - updatedById: currentUser.id, - createdAt: new Date(Date.now() + index * 1000), - })); - - // Bulk create items - const conversions = await db.conversions.bulkCreate(conversionsData, { - transaction, - }); - - // For each item created, replace relation files - - return conversions; - } - - static async update(id, data, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversions = await db.conversions.findByPk(id, {}, { transaction }); - - const updatePayload = {}; - - if (data.input_value !== undefined) - updatePayload.input_value = data.input_value; - - if (data.input_unit !== undefined) - updatePayload.input_unit = data.input_unit; - - if (data.output_value_btu_h !== undefined) - updatePayload.output_value_btu_h = data.output_value_btu_h; - - if (data.output_value_kw !== undefined) - updatePayload.output_value_kw = data.output_value_kw; - - if (data.output_value_kcal_h !== undefined) - updatePayload.output_value_kcal_h = data.output_value_kcal_h; - - if (data.output_value_tr !== undefined) - updatePayload.output_value_tr = data.output_value_tr; - - if (data.output_value_hp !== undefined) - updatePayload.output_value_hp = data.output_value_hp; - - if (data.output_value_w !== undefined) - updatePayload.output_value_w = data.output_value_w; - - updatePayload.updatedById = currentUser.id; - - await conversions.update(updatePayload, { transaction }); - - return conversions; - } - - static async deleteByIds(ids, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversions = await db.conversions.findAll({ - where: { - id: { - [Op.in]: ids, - }, - }, - transaction, - }); - - await db.sequelize.transaction(async (transaction) => { - for (const record of conversions) { - await record.update({ deletedBy: currentUser.id }, { transaction }); - } - for (const record of conversions) { - await record.destroy({ transaction }); - } - }); - - return conversions; - } - - static async remove(id, options) { - const currentUser = (options && options.currentUser) || { id: null }; - const transaction = (options && options.transaction) || undefined; - - const conversions = await db.conversions.findByPk(id, options); - - await conversions.update( - { - deletedBy: currentUser.id, - }, - { - transaction, - }, - ); - - await conversions.destroy({ - transaction, - }); - - return conversions; - } - - static async findBy(where, options) { - const transaction = (options && options.transaction) || undefined; - - const conversions = await db.conversions.findOne( - { where }, - { transaction }, - ); - - if (!conversions) { - return conversions; - } - - const output = conversions.get({ plain: true }); - - return output; - } - - static async findAll(filter, options) { - const limit = filter.limit || 0; - let offset = 0; - let where = {}; - const currentPage = +filter.page; - - offset = currentPage * limit; - - const orderBy = null; - - const transaction = (options && options.transaction) || undefined; - - let include = []; - - if (filter) { - if (filter.id) { - where = { - ...where, - ['id']: Utils.uuid(filter.id), - }; - } - - if (filter.input_valueRange) { - const [start, end] = filter.input_valueRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - input_value: { - ...where.input_value, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - input_value: { - ...where.input_value, - [Op.lte]: end, - }, - }; - } - } - - if (filter.output_value_btu_hRange) { - const [start, end] = filter.output_value_btu_hRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - output_value_btu_h: { - ...where.output_value_btu_h, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - output_value_btu_h: { - ...where.output_value_btu_h, - [Op.lte]: end, - }, - }; - } - } - - if (filter.output_value_kwRange) { - const [start, end] = filter.output_value_kwRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - output_value_kw: { - ...where.output_value_kw, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - output_value_kw: { - ...where.output_value_kw, - [Op.lte]: end, - }, - }; - } - } - - if (filter.output_value_kcal_hRange) { - const [start, end] = filter.output_value_kcal_hRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - output_value_kcal_h: { - ...where.output_value_kcal_h, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - output_value_kcal_h: { - ...where.output_value_kcal_h, - [Op.lte]: end, - }, - }; - } - } - - if (filter.output_value_trRange) { - const [start, end] = filter.output_value_trRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - output_value_tr: { - ...where.output_value_tr, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - output_value_tr: { - ...where.output_value_tr, - [Op.lte]: end, - }, - }; - } - } - - if (filter.output_value_hpRange) { - const [start, end] = filter.output_value_hpRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - output_value_hp: { - ...where.output_value_hp, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - output_value_hp: { - ...where.output_value_hp, - [Op.lte]: end, - }, - }; - } - } - - if (filter.output_value_wRange) { - const [start, end] = filter.output_value_wRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - output_value_w: { - ...where.output_value_w, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - output_value_w: { - ...where.output_value_w, - [Op.lte]: end, - }, - }; - } - } - - if (filter.active !== undefined) { - where = { - ...where, - active: filter.active === true || filter.active === 'true', - }; - } - - if (filter.input_unit) { - where = { - ...where, - input_unit: filter.input_unit, - }; - } - - 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.conversions.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('conversions', 'input_value', query), - ], - }; - } - - const records = await db.conversions.findAll({ - attributes: ['id', 'input_value'], - where, - limit: limit ? Number(limit) : undefined, - offset: offset ? Number(offset) : undefined, - orderBy: [['input_value', 'ASC']], - }); - - return records.map((record) => ({ - id: record.id, - label: record.input_value, - })); - } -}; diff --git a/backend/src/db/migrations/1756752694156.js b/backend/src/db/migrations/1756752694156.js new file mode 100644 index 0000000..3dac2e8 --- /dev/null +++ b/backend/src/db/migrations/1756752694156.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('conversions', { 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( + 'conversions', + { + 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/conversions.js b/backend/src/db/models/conversions.js deleted file mode 100644 index 958b1bf..0000000 --- a/backend/src/db/models/conversions.js +++ /dev/null @@ -1,79 +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 conversions = sequelize.define( - 'conversions', - { - id: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - primaryKey: true, - }, - - input_value: { - type: DataTypes.DECIMAL, - }, - - input_unit: { - type: DataTypes.ENUM, - - values: ['BTU/h', 'kW', 'kcal/h', 'TR', 'HP', 'W'], - }, - - output_value_btu_h: { - type: DataTypes.DECIMAL, - }, - - output_value_kw: { - type: DataTypes.DECIMAL, - }, - - output_value_kcal_h: { - type: DataTypes.DECIMAL, - }, - - output_value_tr: { - type: DataTypes.DECIMAL, - }, - - output_value_hp: { - type: DataTypes.DECIMAL, - }, - - output_value_w: { - type: DataTypes.DECIMAL, - }, - - importHash: { - type: DataTypes.STRING(255), - allowNull: true, - unique: true, - }, - }, - { - timestamps: true, - paranoid: true, - freezeTableName: true, - }, - ); - - conversions.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.conversions.belongsTo(db.users, { - as: 'createdBy', - }); - - db.conversions.belongsTo(db.users, { - as: 'updatedBy', - }); - }; - - return conversions; -}; diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index c9bb113..17c9736 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -93,7 +93,7 @@ module.exports = { ]; } - const entities = ['users', 'conversions', 'roles', 'permissions', ,]; + const entities = ['users', 'roles', 'permissions', ,]; await queryInterface.bulkInsert( 'permissions', entities.flatMap(createPermissions), @@ -154,83 +154,6 @@ primary key ("roles_permissionsId", "permissionId") permissionId: getId('CREATE_USERS'), }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('conversion_manager'), - permissionId: getId('CREATE_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('conversion_manager'), - permissionId: getId('READ_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('conversion_manager'), - permissionId: getId('UPDATE_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('conversion_manager'), - permissionId: getId('DELETE_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('conversion_user'), - permissionId: getId('CREATE_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('conversion_user'), - permissionId: getId('READ_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('guest'), - permissionId: getId('CREATE_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('guest'), - permissionId: getId('READ_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('viewer'), - permissionId: getId('CREATE_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('viewer'), - permissionId: getId('READ_CONVERSIONS'), - }, - - { - createdAt, - updatedAt, - roles_permissionsId: getId('report_viewer'), - permissionId: getId('READ_CONVERSIONS'), - }, - { createdAt, updatedAt, @@ -291,31 +214,6 @@ primary key ("roles_permissionsId", "permissionId") permissionId: getId('DELETE_USERS'), }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_CONVERSIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_CONVERSIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_CONVERSIONS'), - }, - { - createdAt, - updatedAt, - roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_CONVERSIONS'), - }, - { createdAt, updatedAt, diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index 73285c1..2a2db5c 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -1,76 +1,14 @@ const db = require('../models'); const Users = db.users; -const Conversions = db.conversions; - -const ConversionsData = [ - { - input_value: 10000, - - input_unit: 'kcal/h', - - output_value_btu_h: 34121, - - output_value_kw: 10, - - output_value_kcal_h: 8600, - - output_value_tr: 2.84, - - output_value_hp: 3.79, - - output_value_w: 10000, - }, - - { - input_value: 5000, - - input_unit: 'TR', - - output_value_btu_h: 17060.5, - - output_value_kw: 5, - - output_value_kcal_h: 4300, - - output_value_tr: 1.42, - - output_value_hp: 1.89, - - output_value_w: 5000, - }, - - { - input_value: 15000, - - input_unit: 'BTU/h', - - output_value_btu_h: 15000, - - output_value_kw: 4.39, - - output_value_kcal_h: 12900, - - output_value_tr: 1.25, - - output_value_hp: 5.68, - - output_value_w: 15000, - }, -]; - // Similar logic for "relation_many" module.exports = { up: async (queryInterface, Sequelize) => { - await Conversions.bulkCreate(ConversionsData); - await Promise.all([ // Similar logic for "relation_many" ]); }, - down: async (queryInterface, Sequelize) => { - await queryInterface.bulkDelete('conversions', null, {}); - }, + down: async (queryInterface, Sequelize) => {}, }; diff --git a/backend/src/index.js b/backend/src/index.js index 0961698..0dead34 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -19,8 +19,6 @@ const openaiRoutes = require('./routes/openai'); const usersRoutes = require('./routes/users'); -const conversionsRoutes = require('./routes/conversions'); - const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); @@ -96,12 +94,6 @@ app.use( usersRoutes, ); -app.use( - '/api/conversions', - passport.authenticate('jwt', { session: false }), - conversionsRoutes, -); - app.use( '/api/roles', passport.authenticate('jwt', { session: false }), diff --git a/backend/src/routes/conversions.js b/backend/src/routes/conversions.js deleted file mode 100644 index 4dfcfdf..0000000 --- a/backend/src/routes/conversions.js +++ /dev/null @@ -1,471 +0,0 @@ -const express = require('express'); - -const ConversionsService = require('../services/conversions'); -const ConversionsDBApi = require('../db/api/conversions'); -const wrapAsync = require('../helpers').wrapAsync; - -const router = express.Router(); - -const { parse } = require('json2csv'); - -const { checkCrudPermissions } = require('../middlewares/check-permissions'); - -router.use(checkCrudPermissions('conversions')); - -/** - * @swagger - * components: - * schemas: - * Conversions: - * type: object - * properties: - - * input_value: - * type: integer - * format: int64 - * output_value_btu_h: - * type: integer - * format: int64 - * output_value_kw: - * type: integer - * format: int64 - * output_value_kcal_h: - * type: integer - * format: int64 - * output_value_tr: - * type: integer - * format: int64 - * output_value_hp: - * type: integer - * format: int64 - * output_value_w: - * type: integer - * format: int64 - - * - */ - -/** - * @swagger - * tags: - * name: Conversions - * description: The Conversions managing API - */ - -/** - * @swagger - * /api/conversions: - * post: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * 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/Conversions" - * responses: - * 200: - * description: The item was successfully added - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversions" - * 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 ConversionsService.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: [Conversions] - * 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/Conversions" - * responses: - * 200: - * description: The items were successfully imported - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversions" - * 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 ConversionsService.bulkImport(req, res, true, link.host); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversions/{id}: - * put: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * 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/Conversions" - * required: - * - id - * responses: - * 200: - * description: The item data was successfully updated - * content: - * application/json: - * schema: - * $ref: "#/components/schemas/Conversions" - * 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 ConversionsService.update( - req.body.data, - req.body.id, - req.currentUser, - ); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversions/{id}: - * delete: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * 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/Conversions" - * 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 ConversionsService.remove(req.params.id, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversions/deleteByIds: - * post: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * 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/Conversions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Items not found - * 500: - * description: Some server error - */ -router.post( - '/deleteByIds', - wrapAsync(async (req, res) => { - await ConversionsService.deleteByIds(req.body.data, req.currentUser); - const payload = true; - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversions: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * summary: Get all conversions - * description: Get all conversions - * responses: - * 200: - * description: Conversions list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Conversions" - * 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 ConversionsDBApi.findAll(req.query, { currentUser }); - if (filetype && filetype === 'csv') { - const fields = [ - 'id', - - 'input_value', - 'output_value_btu_h', - 'output_value_kw', - 'output_value_kcal_h', - 'output_value_tr', - 'output_value_hp', - 'output_value_w', - ]; - 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/conversions/count: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * summary: Count all conversions - * description: Count all conversions - * responses: - * 200: - * description: Conversions count successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Conversions" - * 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 ConversionsDBApi.findAll(req.query, null, { - countOnly: true, - currentUser, - }); - - res.status(200).send(payload); - }), -); - -/** - * @swagger - * /api/conversions/autocomplete: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * summary: Find all conversions that match search criteria - * description: Find all conversions that match search criteria - * responses: - * 200: - * description: Conversions list successfully received - * content: - * application/json: - * schema: - * type: array - * items: - * $ref: "#/components/schemas/Conversions" - * 401: - * $ref: "#/components/responses/UnauthorizedError" - * 404: - * description: Data not found - * 500: - * description: Some server error - */ -router.get('/autocomplete', async (req, res) => { - const payload = await ConversionsDBApi.findAllAutocomplete( - req.query.query, - req.query.limit, - req.query.offset, - ); - - res.status(200).send(payload); -}); - -/** - * @swagger - * /api/conversions/{id}: - * get: - * security: - * - bearerAuth: [] - * tags: [Conversions] - * 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/Conversions" - * 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 ConversionsDBApi.findBy({ id: req.params.id }); - - res.status(200).send(payload); - }), -); - -router.use('/', require('../helpers').commonErrorHandler); - -module.exports = router; diff --git a/backend/src/services/conversions.js b/backend/src/services/conversions.js deleted file mode 100644 index e1bf9e7..0000000 --- a/backend/src/services/conversions.js +++ /dev/null @@ -1,114 +0,0 @@ -const db = require('../db/models'); -const ConversionsDBApi = require('../db/api/conversions'); -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 ConversionsService { - static async create(data, currentUser) { - const transaction = await db.sequelize.transaction(); - try { - await ConversionsDBApi.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 ConversionsDBApi.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 conversions = await ConversionsDBApi.findBy({ id }, { transaction }); - - if (!conversions) { - throw new ValidationError('conversionsNotFound'); - } - - const updatedConversions = await ConversionsDBApi.update(id, data, { - currentUser, - transaction, - }); - - await transaction.commit(); - return updatedConversions; - } catch (error) { - await transaction.rollback(); - throw error; - } - } - - static async deleteByIds(ids, currentUser) { - const transaction = await db.sequelize.transaction(); - - try { - await ConversionsDBApi.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 ConversionsDBApi.remove(id, { - currentUser, - transaction, - }); - - await transaction.commit(); - } catch (error) { - await transaction.rollback(); - throw error; - } - } -}; diff --git a/backend/src/services/search.js b/backend/src/services/search.js index 3e2b82c..e37e9f6 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -43,23 +43,7 @@ module.exports = class SearchService { const tableColumns = { users: ['firstName', 'lastName', 'phoneNumber', 'email'], }; - const columnsInt = { - conversions: [ - 'input_value', - - 'output_value_btu_h', - - 'output_value_kw', - - 'output_value_kcal_h', - - 'output_value_tr', - - 'output_value_hp', - - 'output_value_w', - ], - }; + const columnsInt = {}; let allFoundRecords = []; 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/Conversions/CardConversions.tsx b/frontend/src/components/Conversions/CardConversions.tsx deleted file mode 100644 index 823ee39..0000000 --- a/frontend/src/components/Conversions/CardConversions.tsx +++ /dev/null @@ -1,186 +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 = { - conversions: any[]; - loading: boolean; - onDelete: (id: string) => void; - currentPage: number; - numPages: number; - onPageChange: (page: number) => void; -}; - -const CardConversions = ({ - conversions, - 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_CONVERSIONS'); - - return ( -
- {loading && } -
    - {!loading && - conversions.map((item, index) => ( -
  • -
    - - {item.input_value} - - -
    - -
    -
    -
    -
    -
    - InputValue -
    -
    -
    - {item.input_value} -
    -
    -
    - -
    -
    - InputUnit -
    -
    -
    - {item.input_unit} -
    -
    -
    - -
    -
    - OutputValueBTU/h -
    -
    -
    - {item.output_value_btu_h} -
    -
    -
    - -
    -
    - OutputValuekW -
    -
    -
    - {item.output_value_kw} -
    -
    -
    - -
    -
    - OutputValuekcal/h -
    -
    -
    - {item.output_value_kcal_h} -
    -
    -
    - -
    -
    - OutputValueTR -
    -
    -
    - {item.output_value_tr} -
    -
    -
    - -
    -
    - OutputValueHP -
    -
    -
    - {item.output_value_hp} -
    -
    -
    - -
    -
    - OutputValueW -
    -
    -
    - {item.output_value_w} -
    -
    -
    -
    -
  • - ))} - {!loading && conversions.length === 0 && ( -
    -

    No data to display

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

InputValue

-

{item.input_value}

-
- -
-

InputUnit

-

{item.input_unit}

-
- -
-

- OutputValueBTU/h -

-

- {item.output_value_btu_h} -

-
- -
-

- OutputValuekW -

-

{item.output_value_kw}

-
- -
-

- OutputValuekcal/h -

-

- {item.output_value_kcal_h} -

-
- -
-

- OutputValueTR -

-

{item.output_value_tr}

-
- -
-

- OutputValueHP -

-

{item.output_value_hp}

-
- -
-

OutputValueW

-

{item.output_value_w}

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

No data to display

-
- )} -
-
- -
- - ); -}; - -export default ListConversions; diff --git a/frontend/src/components/Conversions/TableConversions.tsx b/frontend/src/components/Conversions/TableConversions.tsx deleted file mode 100644 index 589365d..0000000 --- a/frontend/src/components/Conversions/TableConversions.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/conversions/conversionsSlice'; -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 './configureConversionsCols'; -import _ from 'lodash'; -import dataFormatter from '../../helpers/dataFormatter'; -import { dataGridStyles } from '../../styles'; - -const perPage = 10; - -const TableSampleConversions = ({ - 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 { - conversions, - loading, - count, - notify: conversionsNotify, - refetch, - } = useAppSelector((state) => state.conversions); - 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 (conversionsNotify.showNotification) { - notify( - conversionsNotify.typeNotification, - conversionsNotify.textNotification, - ); - } - }, [conversionsNotify.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, `conversions`, 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={conversions ?? []} - 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 TableSampleConversions; diff --git a/frontend/src/components/Conversions/configureConversionsCols.tsx b/frontend/src/components/Conversions/configureConversionsCols.tsx deleted file mode 100644 index df6c3c0..0000000 --- a/frontend/src/components/Conversions/configureConversionsCols.tsx +++ /dev/null @@ -1,172 +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_CONVERSIONS'); - - return [ - { - field: 'input_value', - headerName: 'InputValue', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'input_unit', - headerName: 'InputUnit', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - - { - field: 'output_value_btu_h', - headerName: 'OutputValueBTU/h', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'output_value_kw', - headerName: 'OutputValuekW', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'output_value_kcal_h', - headerName: 'OutputValuekcal/h', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'output_value_tr', - headerName: 'OutputValueTR', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'output_value_hp', - headerName: 'OutputValueHP', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'output_value_w', - headerName: 'OutputValueW', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'number', - }, - - { - field: 'actions', - type: 'actions', - minWidth: 30, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - getActions: (params: GridRowParams) => { - return [ -
- -
, - ]; - }, - }, - ]; -}; diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index bee98f3..ccf9749 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -16,17 +16,6 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiAccountGroup ?? icon.mdiTable, permissions: 'READ_USERS', }, - { - href: '/conversions/conversions-list', - label: 'Conversions', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - icon: - 'mdiSwapHorizontal' in icon - ? icon['mdiSwapHorizontal' as keyof typeof icon] - : icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_CONVERSIONS', - }, { href: '/roles/roles-list', label: 'Roles', diff --git a/frontend/src/pages/conversions/[conversionsId].tsx b/frontend/src/pages/conversions/[conversionsId].tsx deleted file mode 100644 index 1938d33..0000000 --- a/frontend/src/pages/conversions/[conversionsId].tsx +++ /dev/null @@ -1,208 +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/conversions/conversionsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; - -const EditConversions = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - input_value: '', - - input_unit: '', - - output_value_btu_h: '', - - output_value_kw: '', - - output_value_kcal_h: '', - - output_value_tr: '', - - output_value_hp: '', - - output_value_w: '', - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { conversions } = useAppSelector((state) => state.conversions); - - const { conversionsId } = router.query; - - useEffect(() => { - dispatch(fetch({ id: conversionsId })); - }, [conversionsId]); - - useEffect(() => { - if (typeof conversions === 'object') { - setInitialValues(conversions); - } - }, [conversions]); - - useEffect(() => { - if (typeof conversions === 'object') { - const newInitialVal = { ...initVals }; - - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = conversions[el]), - ); - - setInitialValues(newInitialVal); - } - }, [conversions]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: conversionsId, data })); - await router.push('/conversions/conversions-list'); - }; - - return ( - <> - - {getPageTitle('Edit conversions')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/conversions/conversions-list')} - /> - - -
-
-
- - ); -}; - -EditConversions.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditConversions; diff --git a/frontend/src/pages/conversions/conversions-edit.tsx b/frontend/src/pages/conversions/conversions-edit.tsx deleted file mode 100644 index 4ca5a56..0000000 --- a/frontend/src/pages/conversions/conversions-edit.tsx +++ /dev/null @@ -1,206 +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/conversions/conversionsSlice'; -import { useAppDispatch, useAppSelector } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; - -const EditConversionsPage = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const initVals = { - input_value: '', - - input_unit: '', - - output_value_btu_h: '', - - output_value_kw: '', - - output_value_kcal_h: '', - - output_value_tr: '', - - output_value_hp: '', - - output_value_w: '', - }; - const [initialValues, setInitialValues] = useState(initVals); - - const { conversions } = useAppSelector((state) => state.conversions); - - const { id } = router.query; - - useEffect(() => { - dispatch(fetch({ id: id })); - }, [id]); - - useEffect(() => { - if (typeof conversions === 'object') { - setInitialValues(conversions); - } - }, [conversions]); - - useEffect(() => { - if (typeof conversions === 'object') { - const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = conversions[el]), - ); - setInitialValues(newInitialVal); - } - }, [conversions]); - - const handleSubmit = async (data) => { - await dispatch(update({ id: id, data })); - await router.push('/conversions/conversions-list'); - }; - - return ( - <> - - {getPageTitle('Edit conversions')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/conversions/conversions-list')} - /> - - -
-
-
- - ); -}; - -EditConversionsPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default EditConversionsPage; diff --git a/frontend/src/pages/conversions/conversions-list.tsx b/frontend/src/pages/conversions/conversions-list.tsx deleted file mode 100644 index 4578553..0000000 --- a/frontend/src/pages/conversions/conversions-list.tsx +++ /dev/null @@ -1,184 +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 TableConversions from '../../components/Conversions/TableConversions'; -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/conversions/conversionsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const ConversionsTablesPage = () => { - 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: 'InputValue', title: 'input_value', number: 'true' }, - { label: 'OutputValueBTU/h', title: 'output_value_btu_h', number: 'true' }, - { label: 'OutputValuekW', title: 'output_value_kw', number: 'true' }, - { - label: 'OutputValuekcal/h', - title: 'output_value_kcal_h', - number: 'true', - }, - { label: 'OutputValueTR', title: 'output_value_tr', number: 'true' }, - { label: 'OutputValueHP', title: 'output_value_hp', number: 'true' }, - { label: 'OutputValueW', title: 'output_value_w', number: 'true' }, - - { - label: 'InputUnit', - title: 'input_unit', - type: 'enum', - options: ['BTU/h', 'kW', 'kcal/h', 'TR', 'HP', 'W'], - }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_CONVERSIONS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getConversionsCSV = async () => { - const response = await axios({ - url: '/conversions?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 = 'conversionsCSV.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('Conversions')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
-
-
- - - - -
- - - - - ); -}; - -ConversionsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversionsTablesPage; diff --git a/frontend/src/pages/conversions/conversions-new.tsx b/frontend/src/pages/conversions/conversions-new.tsx deleted file mode 100644 index 4d1b1c2..0000000 --- a/frontend/src/pages/conversions/conversions-new.tsx +++ /dev/null @@ -1,180 +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/conversions/conversionsSlice'; -import { useAppDispatch } from '../../stores/hooks'; -import { useRouter } from 'next/router'; -import moment from 'moment'; - -const initialValues = { - input_value: '', - - input_unit: 'BTU/h', - - output_value_btu_h: '', - - output_value_kw: '', - - output_value_kcal_h: '', - - output_value_tr: '', - - output_value_hp: '', - - output_value_w: '', -}; - -const ConversionsNew = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - - const handleSubmit = async (data) => { - await dispatch(create(data)); - await router.push('/conversions/conversions-list'); - }; - return ( - <> - - {getPageTitle('New Item')} - - - - {''} - - - handleSubmit(values)} - > -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - router.push('/conversions/conversions-list')} - /> - - -
-
-
- - ); -}; - -ConversionsNew.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversionsNew; diff --git a/frontend/src/pages/conversions/conversions-table.tsx b/frontend/src/pages/conversions/conversions-table.tsx deleted file mode 100644 index f0eb6df..0000000 --- a/frontend/src/pages/conversions/conversions-table.tsx +++ /dev/null @@ -1,187 +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 TableConversions from '../../components/Conversions/TableConversions'; -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/conversions/conversionsSlice'; - -import { hasPermission } from '../../helpers/userPermissions'; - -const ConversionsTablesPage = () => { - 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: 'InputValue', title: 'input_value', number: 'true' }, - { label: 'OutputValueBTU/h', title: 'output_value_btu_h', number: 'true' }, - { label: 'OutputValuekW', title: 'output_value_kw', number: 'true' }, - { - label: 'OutputValuekcal/h', - title: 'output_value_kcal_h', - number: 'true', - }, - { label: 'OutputValueTR', title: 'output_value_tr', number: 'true' }, - { label: 'OutputValueHP', title: 'output_value_hp', number: 'true' }, - { label: 'OutputValueW', title: 'output_value_w', number: 'true' }, - - { - label: 'InputUnit', - title: 'input_unit', - type: 'enum', - options: ['BTU/h', 'kW', 'kcal/h', 'TR', 'HP', 'W'], - }, - ]); - - const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_CONVERSIONS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; - - const getConversionsCSV = async () => { - const response = await axios({ - url: '/conversions?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 = 'conversionsCSV.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('Conversions')} - - - - {''} - - - {hasCreatePermission && ( - - )} - - - - - {hasCreatePermission && ( - setIsModalActive(true)} - /> - )} - -
-
- - - Back to table - -
-
- - - -
- - - - - ); -}; - -ConversionsTablesPage.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversionsTablesPage; diff --git a/frontend/src/pages/conversions/conversions-view.tsx b/frontend/src/pages/conversions/conversions-view.tsx deleted file mode 100644 index 3ef610c..0000000 --- a/frontend/src/pages/conversions/conversions-view.tsx +++ /dev/null @@ -1,118 +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/conversions/conversionsSlice'; -import { saveFile } from '../../helpers/fileSaver'; -import dataFormatter from '../../helpers/dataFormatter'; -import ImageField from '../../components/ImageField'; -import LayoutAuthenticated from '../../layouts/Authenticated'; -import { getPageTitle } from '../../config'; -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; -import SectionMain from '../../components/SectionMain'; -import CardBox from '../../components/CardBox'; -import BaseButton from '../../components/BaseButton'; -import BaseDivider from '../../components/BaseDivider'; -import { mdiChartTimelineVariant } from '@mdi/js'; -import { SwitchField } from '../../components/SwitchField'; -import FormField from '../../components/FormField'; - -const ConversionsView = () => { - const router = useRouter(); - const dispatch = useAppDispatch(); - const { conversions } = useAppSelector((state) => state.conversions); - - 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 conversions')} - - - - - - -
-

InputValue

-

{conversions?.input_value || 'No data'}

-
- -
-

InputUnit

-

{conversions?.input_unit ?? 'No data'}

-
- -
-

OutputValueBTU/h

-

{conversions?.output_value_btu_h || 'No data'}

-
- -
-

OutputValuekW

-

{conversions?.output_value_kw || 'No data'}

-
- -
-

OutputValuekcal/h

-

{conversions?.output_value_kcal_h || 'No data'}

-
- -
-

OutputValueTR

-

{conversions?.output_value_tr || 'No data'}

-
- -
-

OutputValueHP

-

{conversions?.output_value_hp || 'No data'}

-
- -
-

OutputValueW

-

{conversions?.output_value_w || 'No data'}

-
- - - - router.push('/conversions/conversions-list')} - /> -
-
- - ); -}; - -ConversionsView.getLayout = function getLayout(page: ReactElement) { - return ( - - {page} - - ); -}; - -export default ConversionsView; diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 7ba560a..ad290de 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -29,7 +29,6 @@ const Dashboard = () => { }); const [users, setUsers] = React.useState(loadingMessage); - const [conversions, setConversions] = React.useState(loadingMessage); const [roles, setRoles] = React.useState(loadingMessage); const [permissions, setPermissions] = React.useState(loadingMessage); @@ -42,8 +41,8 @@ const Dashboard = () => { const { rolesWidgets, loading } = useAppSelector((state) => state.roles); async function loadData() { - const entities = ['users', 'conversions', 'roles', 'permissions']; - const fns = [setUsers, setConversions, setRoles, setPermissions]; + const entities = ['users', 'roles', 'permissions']; + const fns = [setUsers, setRoles, setPermissions]; const requests = entities.map((entity, index) => { if (hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) { @@ -187,42 +186,6 @@ const Dashboard = () => { )} - {hasPermission(currentUser, 'READ_CONVERSIONS') && ( - -
-
-
-
- Conversions -
-
- {conversions} -
-
-
- -
-
-
- - )} - {hasPermission(currentUser, 'READ_ROLES') && (
{ - const { id, query } = data; - const result = await axios.get( - `conversions${query || (id ? `/${id}` : '')}`, - ); - return id - ? result.data - : { rows: result.data.rows, count: result.data.count }; - }, -); - -export const deleteItemsByIds = createAsyncThunk( - 'conversions/deleteByIds', - async (data: any, { rejectWithValue }) => { - try { - await axios.post('conversions/deleteByIds', { data }); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const deleteItem = createAsyncThunk( - 'conversions/deleteConversions', - async (id: string, { rejectWithValue }) => { - try { - await axios.delete(`conversions/${id}`); - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const create = createAsyncThunk( - 'conversions/createConversions', - async (data: any, { rejectWithValue }) => { - try { - const result = await axios.post('conversions', { data }); - return result.data; - } catch (error) { - if (!error.response) { - throw error; - } - - return rejectWithValue(error.response.data); - } - }, -); - -export const uploadCsv = createAsyncThunk( - 'conversions/uploadCsv', - async (file: File, { rejectWithValue }) => { - try { - const data = new FormData(); - data.append('file', file); - data.append('filename', file.name); - - const result = await axios.post('conversions/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( - 'conversions/updateConversions', - async (payload: any, { rejectWithValue }) => { - try { - const result = await axios.put(`conversions/${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 conversionsSlice = createSlice({ - name: 'conversions', - 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.conversions = action.payload.rows; - state.count = action.payload.count; - } else { - state.conversions = 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, 'Conversions 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, `${'Conversions'.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, `${'Conversions'.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, `${'Conversions'.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, 'Conversions 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 } = conversionsSlice.actions; - -export default conversionsSlice.reducer; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index 5953e10..33983e9 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -5,7 +5,6 @@ import authSlice from './authSlice'; import openAiSlice from './openAiSlice'; import usersSlice from './users/usersSlice'; -import conversionsSlice from './conversions/conversionsSlice'; import rolesSlice from './roles/rolesSlice'; import permissionsSlice from './permissions/permissionsSlice'; @@ -17,7 +16,6 @@ export const store = configureStore({ openAi: openAiSlice, users: usersSlice, - conversions: conversionsSlice, roles: rolesSlice, permissions: permissionsSlice, },