From bb12d4a2a1f10a1fa35208b84468807a53a3fa3e Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 9 May 2025 09:30:01 +0000 Subject: [PATCH] add likes entity --- app-shell/src/_schema.json | 3 +- backend/src/db/api/likes.js | 246 +++++++++ backend/src/db/migrations/1746782697031.js | 72 +++ backend/src/db/migrations/1746782735187.js | 47 ++ backend/src/db/models/likes.js | 49 ++ .../db/seeders/20200430130760-user-roles.js | 26 + .../db/seeders/20231127130745-sample-data.js | 136 +---- backend/src/db/seeders/20250509092457.js | 87 ++++ backend/src/index.js | 8 + backend/src/routes/likes.js | 433 ++++++++++++++++ backend/src/services/likes.js | 114 +++++ backend/src/services/search.js | 2 + frontend/src/components/Likes/CardLikes.tsx | 109 ++++ frontend/src/components/Likes/ListLikes.tsx | 90 ++++ frontend/src/components/Likes/TableLikes.tsx | 481 ++++++++++++++++++ .../components/Likes/configureLikesCols.tsx | 73 +++ .../components/WebPageComponents/Header.tsx | 2 +- frontend/src/menuAside.ts | 14 +- frontend/src/pages/dashboard.tsx | 35 ++ frontend/src/pages/index.tsx | 2 +- frontend/src/pages/likes/[likesId].tsx | 124 +++++ frontend/src/pages/likes/likes-edit.tsx | 122 +++++ frontend/src/pages/likes/likes-list.tsx | 160 ++++++ frontend/src/pages/likes/likes-new.tsx | 98 ++++ frontend/src/pages/likes/likes-table.tsx | 159 ++++++ frontend/src/pages/likes/likes-view.tsx | 81 +++ frontend/src/pages/web_pages/pricing.tsx | 2 +- frontend/src/pages/web_pages/services.tsx | 2 +- frontend/src/stores/likes/likesSlice.ts | 236 +++++++++ frontend/src/stores/store.ts | 2 + 30 files changed, 2889 insertions(+), 126 deletions(-) create mode 100644 backend/src/db/api/likes.js create mode 100644 backend/src/db/migrations/1746782697031.js create mode 100644 backend/src/db/migrations/1746782735187.js create mode 100644 backend/src/db/models/likes.js create mode 100644 backend/src/db/seeders/20250509092457.js create mode 100644 backend/src/routes/likes.js create mode 100644 backend/src/services/likes.js create mode 100644 frontend/src/components/Likes/CardLikes.tsx create mode 100644 frontend/src/components/Likes/ListLikes.tsx create mode 100644 frontend/src/components/Likes/TableLikes.tsx create mode 100644 frontend/src/components/Likes/configureLikesCols.tsx create mode 100644 frontend/src/pages/likes/[likesId].tsx create mode 100644 frontend/src/pages/likes/likes-edit.tsx create mode 100644 frontend/src/pages/likes/likes-list.tsx create mode 100644 frontend/src/pages/likes/likes-new.tsx create mode 100644 frontend/src/pages/likes/likes-table.tsx create mode 100644 frontend/src/pages/likes/likes-view.tsx create mode 100644 frontend/src/stores/likes/likesSlice.ts diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json index 369da2b..dc2db4c 100644 --- a/app-shell/src/_schema.json +++ b/app-shell/src/_schema.json @@ -1,5 +1,6 @@ { "Initial version": "{\"iv\":\"e2Z96SPdsyiW4gYS\",\"encryptedData\":\"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\"}", "user info page": "{\"iv\":\"2JM3h1CTtASDF/1m\",\"encryptedData\":\"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\"}", - "border 0": "{\"iv\":\"SM7hI8b8ouVhDoob\",\"encryptedData\":\"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\"}" + "border 0": "{\"iv\":\"SM7hI8b8ouVhDoob\",\"encryptedData\":\"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\"}", + "add likes entity": "{\"iv\":\"CSkB19GuR1HM/ms8\",\"encryptedData\":\"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\"}" } \ No newline at end of file diff --git a/backend/src/db/api/likes.js b/backend/src/db/api/likes.js new file mode 100644 index 0000000..e76fe0b --- /dev/null +++ b/backend/src/db/api/likes.js @@ -0,0 +1,246 @@ +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 LikesDBApi { + static async create(data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const likes = await db.likes.create( + { + id: data.id || undefined, + + amount: data.amount || null, + importHash: data.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + }, + { transaction }, + ); + + return likes; + } + + 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 likesData = data.map((item, index) => ({ + id: item.id || undefined, + + amount: item.amount || null, + importHash: item.importHash || null, + createdById: currentUser.id, + updatedById: currentUser.id, + createdAt: new Date(Date.now() + index * 1000), + })); + + // Bulk create items + const likes = await db.likes.bulkCreate(likesData, { transaction }); + + // For each item created, replace relation files + + return likes; + } + + static async update(id, data, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const likes = await db.likes.findByPk(id, {}, { transaction }); + + const updatePayload = {}; + + if (data.amount !== undefined) updatePayload.amount = data.amount; + + updatePayload.updatedById = currentUser.id; + + await likes.update(updatePayload, { transaction }); + + return likes; + } + + static async deleteByIds(ids, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const likes = await db.likes.findAll({ + where: { + id: { + [Op.in]: ids, + }, + }, + transaction, + }); + + await db.sequelize.transaction(async (transaction) => { + for (const record of likes) { + await record.update({ deletedBy: currentUser.id }, { transaction }); + } + for (const record of likes) { + await record.destroy({ transaction }); + } + }); + + return likes; + } + + static async remove(id, options) { + const currentUser = (options && options.currentUser) || { id: null }; + const transaction = (options && options.transaction) || undefined; + + const likes = await db.likes.findByPk(id, options); + + await likes.update( + { + deletedBy: currentUser.id, + }, + { + transaction, + }, + ); + + await likes.destroy({ + transaction, + }); + + return likes; + } + + static async findBy(where, options) { + const transaction = (options && options.transaction) || undefined; + + const likes = await db.likes.findOne({ where }, { transaction }); + + if (!likes) { + return likes; + } + + const output = likes.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.amount) { + where = { + ...where, + [Op.and]: Utils.ilike('likes', 'amount', filter.amount), + }; + } + + 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.likes.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('likes', 'amount', query), + ], + }; + } + + const records = await db.likes.findAll({ + attributes: ['id', 'amount'], + where, + limit: limit ? Number(limit) : undefined, + offset: offset ? Number(offset) : undefined, + orderBy: [['amount', 'ASC']], + }); + + return records.map((record) => ({ + id: record.id, + label: record.amount, + })); + } +}; diff --git a/backend/src/db/migrations/1746782697031.js b/backend/src/db/migrations/1746782697031.js new file mode 100644 index 0000000..3ab60dc --- /dev/null +++ b/backend/src/db/migrations/1746782697031.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.createTable( + 'likes', + { + 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; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.dropTable('likes', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/migrations/1746782735187.js b/backend/src/db/migrations/1746782735187.js new file mode 100644 index 0000000..628c42f --- /dev/null +++ b/backend/src/db/migrations/1746782735187.js @@ -0,0 +1,47 @@ +module.exports = { + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async up(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.addColumn( + 'likes', + 'amount', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, + /** + * @param {QueryInterface} queryInterface + * @param {Sequelize} Sequelize + * @returns {Promise} + */ + async down(queryInterface, Sequelize) { + /** + * @type {Transaction} + */ + const transaction = await queryInterface.sequelize.transaction(); + try { + await queryInterface.removeColumn('likes', 'amount', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/models/likes.js b/backend/src/db/models/likes.js new file mode 100644 index 0000000..c5e8621 --- /dev/null +++ b/backend/src/db/models/likes.js @@ -0,0 +1,49 @@ +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 likes = sequelize.define( + 'likes', + { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + primaryKey: true, + }, + + amount: { + type: DataTypes.TEXT, + }, + + importHash: { + type: DataTypes.STRING(255), + allowNull: true, + unique: true, + }, + }, + { + timestamps: true, + paranoid: true, + freezeTableName: true, + }, + ); + + likes.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.likes.belongsTo(db.users, { + as: 'createdBy', + }); + + db.likes.belongsTo(db.users, { + as: 'updatedBy', + }); + }; + + return likes; +}; diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index 8a2f255..83a6d5b 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -100,6 +100,7 @@ module.exports = { 'venues', 'roles', 'permissions', + 'likes', , ]; await queryInterface.bulkInsert( @@ -817,6 +818,31 @@ primary key ("roles_permissionsId", "permissionId") permissionId: getId('DELETE_PERMISSIONS'), }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('CREATE_LIKES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('READ_LIKES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('UPDATE_LIKES'), + }, + { + createdAt, + updatedAt, + roles_permissionsId: getId('Administrator'), + permissionId: getId('DELETE_LIKES'), + }, + { createdAt, updatedAt, diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js index bf979f3..148105a 100644 --- a/backend/src/db/seeders/20231127130745-sample-data.js +++ b/backend/src/db/seeders/20231127130745-sample-data.js @@ -11,6 +11,8 @@ const Vendors = db.vendors; const Venues = db.venues; +const Likes = db.likes; + const BudgetsData = [ { name: 'Wedding Budget', @@ -35,22 +37,6 @@ const BudgetsData = [ // type code here for "relation_one" field }, - - { - name: 'Gala Budget', - - amount: 20000, - - // type code here for "relation_one" field - }, - - { - name: 'Launch Budget', - - amount: 10000, - - // type code here for "relation_one" field - }, ]; const GuestsData = [ @@ -59,7 +45,7 @@ const GuestsData = [ is_attending: true, - meal_preference: 'Standard', + meal_preference: 'Vegetarian', }, { @@ -67,7 +53,7 @@ const GuestsData = [ is_attending: true, - meal_preference: 'Vegetarian', + meal_preference: 'Standard', }, { @@ -77,22 +63,6 @@ const GuestsData = [ meal_preference: 'Vegetarian', }, - - { - name: 'Michael Brown', - - is_attending: true, - - meal_preference: 'Standard', - }, - - { - name: 'Sarah Davis', - - is_attending: true, - - meal_preference: 'Standard', - }, ]; const SchedulesData = [ @@ -125,26 +95,6 @@ const SchedulesData = [ // type code here for "relation_many" field }, - - { - title: 'Charity Gala', - - start_time: new Date('2023-12-01T19:00:00Z'), - - end_time: new Date('2023-12-01T23:59:00Z'), - - // type code here for "relation_many" field - }, - - { - title: 'Product Launch', - - start_time: new Date('2023-11-05T10:00:00Z'), - - end_time: new Date('2023-11-05T15:00:00Z'), - - // type code here for "relation_many" field - }, ]; const VendorsData = [ @@ -153,7 +103,7 @@ const VendorsData = [ contact_info: 'info@gourmetcatering.com', - type: 'Entertainer', + type: 'Decorator', rating: 4.5, }, @@ -163,7 +113,7 @@ const VendorsData = [ contact_info: 'contact@elegantdecor.com', - type: 'Decorator', + type: 'Entertainer', rating: 4.7, }, @@ -173,30 +123,10 @@ const VendorsData = [ contact_info: 'djbeats@music.com', - type: 'Decorator', + type: 'Caterer', rating: 4.8, }, - - { - name: 'Floral Arrangements', - - contact_info: 'flowers@floralarrangements.com', - - type: 'Caterer', - - rating: 4.6, - }, - - { - name: 'Party Rentals', - - contact_info: 'rentals@partyrentals.com', - - type: 'Caterer', - - rating: 4.4, - }, ]; const VenuesData = [ @@ -207,7 +137,7 @@ const VenuesData = [ features: 'Stage, Sound System, Lighting', - is_booked: true, + is_booked: false, }, { @@ -227,27 +157,21 @@ const VenuesData = [ features: 'Projector, Wi-Fi', - is_booked: false, + is_booked: true, + }, +]; + +const LikesData = [ + { + amount: 'James Watson', }, { - name: 'Garden Area', - - size: '4000 sq ft', - - features: 'Outdoor Seating, Fountain', - - is_booked: true, + amount: 'Albrecht von Haller', }, { - name: 'Banquet Room', - - size: '2500 sq ft', - - features: 'Buffet Setup, Dance Floor', - - is_booked: true, + amount: 'Archimedes', }, ]; @@ -286,28 +210,6 @@ async function associateBudgetWithEvent() { if (Budget2?.setEvent) { await Budget2.setEvent(relatedEvent2); } - - const relatedEvent3 = await Schedules.findOne({ - offset: Math.floor(Math.random() * (await Schedules.count())), - }); - const Budget3 = await Budgets.findOne({ - order: [['id', 'ASC']], - offset: 3, - }); - if (Budget3?.setEvent) { - await Budget3.setEvent(relatedEvent3); - } - - const relatedEvent4 = await Schedules.findOne({ - offset: Math.floor(Math.random() * (await Schedules.count())), - }); - const Budget4 = await Budgets.findOne({ - order: [['id', 'ASC']], - offset: 4, - }); - if (Budget4?.setEvent) { - await Budget4.setEvent(relatedEvent4); - } } // Similar logic for "relation_many" @@ -324,6 +226,8 @@ module.exports = { await Venues.bulkCreate(VenuesData); + await Likes.bulkCreate(LikesData); + await Promise.all([ // Similar logic for "relation_many" @@ -343,5 +247,7 @@ module.exports = { await queryInterface.bulkDelete('vendors', null, {}); await queryInterface.bulkDelete('venues', null, {}); + + await queryInterface.bulkDelete('likes', null, {}); }, }; diff --git a/backend/src/db/seeders/20250509092457.js b/backend/src/db/seeders/20250509092457.js new file mode 100644 index 0000000..ddd1a6e --- /dev/null +++ b/backend/src/db/seeders/20250509092457.js @@ -0,0 +1,87 @@ +const { v4: uuid } = require('uuid'); +const db = require('../models'); +const Sequelize = require('sequelize'); +const config = require('../../config'); + +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; + } + + /** + * @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 = ['likes']; + + const createdPermissions = entities.flatMap(createPermissions); + + // Add permissions to database + await queryInterface.bulkInsert('permissions', createdPermissions); + // Get permissions ids + const permissionsIds = createdPermissions.map((p) => p.id); + // Get admin role + const adminRole = await db.roles.findOne({ + where: { name: config.roles.admin }, + }); + + if (adminRole) { + // Add permissions to admin role if it exists + await adminRole.addPermissions(permissionsIds); + } + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete( + 'permissions', + entities.flatMap(createPermissions), + ); + }, +}; diff --git a/backend/src/index.js b/backend/src/index.js index 54dbb41..e5db8d2 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -35,6 +35,8 @@ const rolesRoutes = require('./routes/roles'); const permissionsRoutes = require('./routes/permissions'); +const likesRoutes = require('./routes/likes'); + const getBaseUrl = (url) => { if (!url) return ''; return url.endsWith('/api') ? url.slice(0, -4) : url; @@ -148,6 +150,12 @@ app.use( permissionsRoutes, ); +app.use( + '/api/likes', + passport.authenticate('jwt', { session: false }), + likesRoutes, +); + app.use( '/api/openai', passport.authenticate('jwt', { session: false }), diff --git a/backend/src/routes/likes.js b/backend/src/routes/likes.js new file mode 100644 index 0000000..f6033ff --- /dev/null +++ b/backend/src/routes/likes.js @@ -0,0 +1,433 @@ +const express = require('express'); + +const LikesService = require('../services/likes'); +const LikesDBApi = require('../db/api/likes'); +const wrapAsync = require('../helpers').wrapAsync; + +const router = express.Router(); + +const { parse } = require('json2csv'); + +const { checkCrudPermissions } = require('../middlewares/check-permissions'); + +router.use(checkCrudPermissions('likes')); + +/** + * @swagger + * components: + * schemas: + * Likes: + * type: object + * properties: + + * amount: + * type: string + * default: amount + + */ + +/** + * @swagger + * tags: + * name: Likes + * description: The Likes managing API + */ + +/** + * @swagger + * /api/likes: + * post: + * security: + * - bearerAuth: [] + * tags: [Likes] + * 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/Likes" + * responses: + * 200: + * description: The item was successfully added + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Likes" + * 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 LikesService.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: [Likes] + * 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/Likes" + * responses: + * 200: + * description: The items were successfully imported + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Likes" + * 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 LikesService.bulkImport(req, res, true, link.host); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/likes/{id}: + * put: + * security: + * - bearerAuth: [] + * tags: [Likes] + * 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/Likes" + * required: + * - id + * responses: + * 200: + * description: The item data was successfully updated + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/Likes" + * 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 LikesService.update(req.body.data, req.body.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/likes/{id}: + * delete: + * security: + * - bearerAuth: [] + * tags: [Likes] + * 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/Likes" + * 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 LikesService.remove(req.params.id, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/likes/deleteByIds: + * post: + * security: + * - bearerAuth: [] + * tags: [Likes] + * 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/Likes" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Items not found + * 500: + * description: Some server error + */ +router.post( + '/deleteByIds', + wrapAsync(async (req, res) => { + await LikesService.deleteByIds(req.body.data, req.currentUser); + const payload = true; + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/likes: + * get: + * security: + * - bearerAuth: [] + * tags: [Likes] + * summary: Get all likes + * description: Get all likes + * responses: + * 200: + * description: Likes list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Likes" + * 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 LikesDBApi.findAll(req.query, { currentUser }); + if (filetype && filetype === 'csv') { + const fields = ['id', 'amount']; + 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/likes/count: + * get: + * security: + * - bearerAuth: [] + * tags: [Likes] + * summary: Count all likes + * description: Count all likes + * responses: + * 200: + * description: Likes count successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Likes" + * 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 LikesDBApi.findAll(req.query, null, { + countOnly: true, + currentUser, + }); + + res.status(200).send(payload); + }), +); + +/** + * @swagger + * /api/likes/autocomplete: + * get: + * security: + * - bearerAuth: [] + * tags: [Likes] + * summary: Find all likes that match search criteria + * description: Find all likes that match search criteria + * responses: + * 200: + * description: Likes list successfully received + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: "#/components/schemas/Likes" + * 401: + * $ref: "#/components/responses/UnauthorizedError" + * 404: + * description: Data not found + * 500: + * description: Some server error + */ +router.get('/autocomplete', async (req, res) => { + const payload = await LikesDBApi.findAllAutocomplete( + req.query.query, + req.query.limit, + req.query.offset, + ); + + res.status(200).send(payload); +}); + +/** + * @swagger + * /api/likes/{id}: + * get: + * security: + * - bearerAuth: [] + * tags: [Likes] + * 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/Likes" + * 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 LikesDBApi.findBy({ id: req.params.id }); + + res.status(200).send(payload); + }), +); + +router.use('/', require('../helpers').commonErrorHandler); + +module.exports = router; diff --git a/backend/src/services/likes.js b/backend/src/services/likes.js new file mode 100644 index 0000000..d8f6f3e --- /dev/null +++ b/backend/src/services/likes.js @@ -0,0 +1,114 @@ +const db = require('../db/models'); +const LikesDBApi = require('../db/api/likes'); +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 LikesService { + static async create(data, currentUser) { + const transaction = await db.sequelize.transaction(); + try { + await LikesDBApi.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 LikesDBApi.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 likes = await LikesDBApi.findBy({ id }, { transaction }); + + if (!likes) { + throw new ValidationError('likesNotFound'); + } + + const updatedLikes = await LikesDBApi.update(id, data, { + currentUser, + transaction, + }); + + await transaction.commit(); + return updatedLikes; + } catch (error) { + await transaction.rollback(); + throw error; + } + } + + static async deleteByIds(ids, currentUser) { + const transaction = await db.sequelize.transaction(); + + try { + await LikesDBApi.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 LikesDBApi.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 f8b07ab..a7ec9cc 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -52,6 +52,8 @@ module.exports = class SearchService { vendors: ['name', 'contact_info'], venues: ['name', 'size', 'features'], + + likes: ['amount'], }; const columnsInt = { budgets: ['amount'], diff --git a/frontend/src/components/Likes/CardLikes.tsx b/frontend/src/components/Likes/CardLikes.tsx new file mode 100644 index 0000000..0c8e3c2 --- /dev/null +++ b/frontend/src/components/Likes/CardLikes.tsx @@ -0,0 +1,109 @@ +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 = { + likes: any[]; + loading: boolean; + onDelete: (id: string) => void; + currentPage: number; + numPages: number; + onPageChange: (page: number) => void; +}; + +const CardLikes = ({ + likes, + 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_LIKES'); + + return ( +
+ {loading && } +
    + {!loading && + likes.map((item, index) => ( +
  • +
    + + {item.amount} + + +
    + +
    +
    +
    +
    +
    + Amount +
    +
    +
    + {item.amount} +
    +
    +
    +
    +
  • + ))} + {!loading && likes.length === 0 && ( +
    +

    No data to display

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

Amount

+

{item.amount}

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

No data to display

+
+ )} +
+
+ +
+ + ); +}; + +export default ListLikes; diff --git a/frontend/src/components/Likes/TableLikes.tsx b/frontend/src/components/Likes/TableLikes.tsx new file mode 100644 index 0000000..e544926 --- /dev/null +++ b/frontend/src/components/Likes/TableLikes.tsx @@ -0,0 +1,481 @@ +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/likes/likesSlice'; +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 './configureLikesCols'; +import _ from 'lodash'; +import dataFormatter from '../../helpers/dataFormatter'; +import { dataGridStyles } from '../../styles'; + +const perPage = 10; + +const TableSampleLikes = ({ + 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 { + likes, + loading, + count, + notify: likesNotify, + refetch, + } = useAppSelector((state) => state.likes); + 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 (likesNotify.showNotification) { + notify(likesNotify.typeNotification, likesNotify.textNotification); + } + }, [likesNotify.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, `likes`, 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={likes ?? []} + 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 TableSampleLikes; diff --git a/frontend/src/components/Likes/configureLikesCols.tsx b/frontend/src/components/Likes/configureLikesCols.tsx new file mode 100644 index 0000000..1986c44 --- /dev/null +++ b/frontend/src/components/Likes/configureLikesCols.tsx @@ -0,0 +1,73 @@ +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_LIKES'); + + return [ + { + field: 'amount', + headerName: 'Amount', + 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/WebPageComponents/Header.tsx b/frontend/src/components/WebPageComponents/Header.tsx index 1c882e7..14ab067 100644 --- a/frontend/src/components/WebPageComponents/Header.tsx +++ b/frontend/src/components/WebPageComponents/Header.tsx @@ -19,7 +19,7 @@ export default function WebSiteHeader({ const websiteHeder = useAppSelector((state) => state.style.websiteHeder); const borders = useAppSelector((state) => state.style.borders); - const style = HeaderStyle.PAGES_RIGHT; + const style = HeaderStyle.PAGES_LEFT; const design = HeaderDesigns.DEFAULT_DESIGN; return ( diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index efc4f3c..68a7db6 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -87,17 +87,19 @@ const menuAside: MenuAsideItem[] = [ icon: icon.mdiShieldAccountOutline ?? icon.mdiTable, permissions: 'READ_PERMISSIONS', }, + { + href: '/likes/likes-list', + label: 'Likes', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + icon: icon.mdiTable ?? icon.mdiTable, + permissions: 'READ_LIKES', + }, { href: '/profile', label: 'Profile', icon: icon.mdiAccountCircle, }, - { - href: '/user-info', - label: 'User Info', - icon: icon.mdiAccountCircle, - }, - { href: '/home', diff --git a/frontend/src/pages/dashboard.tsx b/frontend/src/pages/dashboard.tsx index 131b8c0..b7b13ee 100644 --- a/frontend/src/pages/dashboard.tsx +++ b/frontend/src/pages/dashboard.tsx @@ -30,6 +30,7 @@ const Dashboard = () => { const [venues, setVenues] = React.useState('Loading...'); const [roles, setRoles] = React.useState('Loading...'); const [permissions, setPermissions] = React.useState('Loading...'); + const [likes, setLikes] = React.useState('Loading...'); const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' }, @@ -49,6 +50,7 @@ const Dashboard = () => { 'venues', 'roles', 'permissions', + 'likes', ]; const fns = [ setUsers, @@ -59,6 +61,7 @@ const Dashboard = () => { setVenues, setRoles, setPermissions, + setLikes, ]; const requests = entities.map((entity, index) => { @@ -442,6 +445,38 @@ const Dashboard = () => { )} + + {hasPermission(currentUser, 'READ_LIKES') && ( + +
+
+
+
+ Likes +
+
+ {likes} +
+
+
+ +
+
+
+ + )} diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 18c7da0..4b2cef5 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -197,7 +197,7 @@ export default function WebSite() { { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + amount: '', + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { likes } = useAppSelector((state) => state.likes); + + const { likesId } = router.query; + + useEffect(() => { + dispatch(fetch({ id: likesId })); + }, [likesId]); + + useEffect(() => { + if (typeof likes === 'object') { + setInitialValues(likes); + } + }, [likes]); + + useEffect(() => { + if (typeof likes === 'object') { + const newInitialVal = { ...initVals }; + + Object.keys(initVals).forEach((el) => (newInitialVal[el] = likes[el])); + + setInitialValues(newInitialVal); + } + }, [likes]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: likesId, data })); + await router.push('/likes/likes-list'); + }; + + return ( + <> + + {getPageTitle('Edit likes')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + router.push('/likes/likes-list')} + /> + + +
+
+
+ + ); +}; + +EditLikes.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default EditLikes; diff --git a/frontend/src/pages/likes/likes-edit.tsx b/frontend/src/pages/likes/likes-edit.tsx new file mode 100644 index 0000000..e5626d0 --- /dev/null +++ b/frontend/src/pages/likes/likes-edit.tsx @@ -0,0 +1,122 @@ +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/likes/likesSlice'; +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 EditLikesPage = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const initVals = { + amount: '', + }; + const [initialValues, setInitialValues] = useState(initVals); + + const { likes } = useAppSelector((state) => state.likes); + + const { id } = router.query; + + useEffect(() => { + dispatch(fetch({ id: id })); + }, [id]); + + useEffect(() => { + if (typeof likes === 'object') { + setInitialValues(likes); + } + }, [likes]); + + useEffect(() => { + if (typeof likes === 'object') { + const newInitialVal = { ...initVals }; + Object.keys(initVals).forEach((el) => (newInitialVal[el] = likes[el])); + setInitialValues(newInitialVal); + } + }, [likes]); + + const handleSubmit = async (data) => { + await dispatch(update({ id: id, data })); + await router.push('/likes/likes-list'); + }; + + return ( + <> + + {getPageTitle('Edit likes')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + router.push('/likes/likes-list')} + /> + + +
+
+
+ + ); +}; + +EditLikesPage.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default EditLikesPage; diff --git a/frontend/src/pages/likes/likes-list.tsx b/frontend/src/pages/likes/likes-list.tsx new file mode 100644 index 0000000..06fcca2 --- /dev/null +++ b/frontend/src/pages/likes/likes-list.tsx @@ -0,0 +1,160 @@ +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 TableLikes from '../../components/Likes/TableLikes'; +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/likes/likesSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const LikesTablesPage = () => { + 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: 'Amount', title: 'amount' }]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_LIKES'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getLikesCSV = async () => { + const response = await axios({ + url: '/likes?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 = 'likesCSV.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('Likes')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + + +
+ + + + + ); +}; + +LikesTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default LikesTablesPage; diff --git a/frontend/src/pages/likes/likes-new.tsx b/frontend/src/pages/likes/likes-new.tsx new file mode 100644 index 0000000..21d974e --- /dev/null +++ b/frontend/src/pages/likes/likes-new.tsx @@ -0,0 +1,98 @@ +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/likes/likesSlice'; +import { useAppDispatch } from '../../stores/hooks'; +import { useRouter } from 'next/router'; +import moment from 'moment'; + +const initialValues = { + amount: '', +}; + +const LikesNew = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + + const handleSubmit = async (data) => { + await dispatch(create(data)); + await router.push('/likes/likes-list'); + }; + return ( + <> + + {getPageTitle('New Item')} + + + + {''} + + + handleSubmit(values)} + > +
+ + + + + + + + + router.push('/likes/likes-list')} + /> + + +
+
+
+ + ); +}; + +LikesNew.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default LikesNew; diff --git a/frontend/src/pages/likes/likes-table.tsx b/frontend/src/pages/likes/likes-table.tsx new file mode 100644 index 0000000..c7869e6 --- /dev/null +++ b/frontend/src/pages/likes/likes-table.tsx @@ -0,0 +1,159 @@ +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 TableLikes from '../../components/Likes/TableLikes'; +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/likes/likesSlice'; + +import { hasPermission } from '../../helpers/userPermissions'; + +const LikesTablesPage = () => { + 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: 'Amount', title: 'amount' }]); + + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_LIKES'); + + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: '', + }, + }; + newItem.fields.selectedField = filters[0].title; + setFilterItems([...filterItems, newItem]); + }; + + const getLikesCSV = async () => { + const response = await axios({ + url: '/likes?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 = 'likesCSV.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('Likes')} + + + + {''} + + + {hasCreatePermission && ( + + )} + + + + + {hasCreatePermission && ( + setIsModalActive(true)} + /> + )} + +
+
+
+
+ + + +
+ + + + + ); +}; + +LikesTablesPage.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default LikesTablesPage; diff --git a/frontend/src/pages/likes/likes-view.tsx b/frontend/src/pages/likes/likes-view.tsx new file mode 100644 index 0000000..9996c4f --- /dev/null +++ b/frontend/src/pages/likes/likes-view.tsx @@ -0,0 +1,81 @@ +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/likes/likesSlice'; +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 LikesView = () => { + const router = useRouter(); + const dispatch = useAppDispatch(); + const { likes } = useAppSelector((state) => state.likes); + + 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 likes')} + + + + + + +
+

Amount

+

{likes?.amount}

+
+ + + + router.push('/likes/likes-list')} + /> +
+
+ + ); +}; + +LikesView.getLayout = function getLayout(page: ReactElement) { + return ( + {page} + ); +}; + +export default LikesView; diff --git a/frontend/src/pages/web_pages/pricing.tsx b/frontend/src/pages/web_pages/pricing.tsx index ad830b4..cc0d4b5 100644 --- a/frontend/src/pages/web_pages/pricing.tsx +++ b/frontend/src/pages/web_pages/pricing.tsx @@ -156,7 +156,7 @@ export default function WebSite() { diff --git a/frontend/src/pages/web_pages/services.tsx b/frontend/src/pages/web_pages/services.tsx index 930bdff..deaff06 100644 --- a/frontend/src/pages/web_pages/services.tsx +++ b/frontend/src/pages/web_pages/services.tsx @@ -170,7 +170,7 @@ export default function WebSite() { diff --git a/frontend/src/stores/likes/likesSlice.ts b/frontend/src/stores/likes/likesSlice.ts new file mode 100644 index 0000000..aadbd82 --- /dev/null +++ b/frontend/src/stores/likes/likesSlice.ts @@ -0,0 +1,236 @@ +import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; +import axios from 'axios'; +import { + fulfilledNotify, + rejectNotify, + resetNotify, +} from '../../helpers/notifyStateHandler'; + +interface MainState { + likes: any; + loading: boolean; + count: number; + refetch: boolean; + rolesWidgets: any[]; + notify: { + showNotification: boolean; + textNotification: string; + typeNotification: string; + }; +} + +const initialState: MainState = { + likes: [], + loading: false, + count: 0, + refetch: false, + rolesWidgets: [], + notify: { + showNotification: false, + textNotification: '', + typeNotification: 'warn', + }, +}; + +export const fetch = createAsyncThunk('likes/fetch', async (data: any) => { + const { id, query } = data; + const result = await axios.get(`likes${query || (id ? `/${id}` : '')}`); + return id + ? result.data + : { rows: result.data.rows, count: result.data.count }; +}); + +export const deleteItemsByIds = createAsyncThunk( + 'likes/deleteByIds', + async (data: any, { rejectWithValue }) => { + try { + await axios.post('likes/deleteByIds', { data }); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const deleteItem = createAsyncThunk( + 'likes/deleteLikes', + async (id: string, { rejectWithValue }) => { + try { + await axios.delete(`likes/${id}`); + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const create = createAsyncThunk( + 'likes/createLikes', + async (data: any, { rejectWithValue }) => { + try { + const result = await axios.post('likes', { data }); + return result.data; + } catch (error) { + if (!error.response) { + throw error; + } + + return rejectWithValue(error.response.data); + } + }, +); + +export const uploadCsv = createAsyncThunk( + 'likes/uploadCsv', + async (file: File, { rejectWithValue }) => { + try { + const data = new FormData(); + data.append('file', file); + data.append('filename', file.name); + + const result = await axios.post('likes/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( + 'likes/updateLikes', + async (payload: any, { rejectWithValue }) => { + try { + const result = await axios.put(`likes/${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 likesSlice = createSlice({ + name: 'likes', + 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.likes = action.payload.rows; + state.count = action.payload.count; + } else { + state.likes = 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, 'Likes 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, `${'Likes'.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, `${'Likes'.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, `${'Likes'.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, 'Likes 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 } = likesSlice.actions; + +export default likesSlice.reducer; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index 71148cc..7a6d5ef 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -12,6 +12,7 @@ import vendorsSlice from './vendors/vendorsSlice'; import venuesSlice from './venues/venuesSlice'; import rolesSlice from './roles/rolesSlice'; import permissionsSlice from './permissions/permissionsSlice'; +import likesSlice from './likes/likesSlice'; export const store = configureStore({ reducer: { @@ -28,6 +29,7 @@ export const store = configureStore({ venues: venuesSlice, roles: rolesSlice, permissions: permissionsSlice, + likes: likesSlice, }, });