diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json deleted file mode 100644 index 8e69c5e..0000000 --- a/app-shell/src/_schema.json +++ /dev/null @@ -1,5 +0,0 @@ - - -{ - "Initial version": "{\"iv\":\"ie3hOBAluBqc+Nw+\",\"encryptedData\":\"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\"}" -} diff --git a/app-shell/src/_schema.json.erb b/app-shell/src/_schema.json.erb new file mode 100644 index 0000000..f24b1e2 --- /dev/null +++ b/app-shell/src/_schema.json.erb @@ -0,0 +1,44 @@ +<% + require 'openssl' + require 'base64' +%> +<% + minimal_schema = { + "entities" => @schema["entities"], + "migrations" => @schema["migrations"] + } + json_string = minimal_schema.to_json.gsub(/"@(\w+)":/, '"\1":') +%> +<% + key_b64 = 'oAYZbSQ7otWLWy/1v6KGwzcpfe4LxdtkDcAUD7HAaw0=' + raise "SCHEMA_ENCRYPTION_KEY environment variable is not set or empty" if key_b64.nil? || key_b64.empty? + + key = Base64.strict_decode64(key_b64) + raise "Invalid key length: expected 32 bytes for AES-256-GCM" unless key.bytesize == 32 + + cipher = OpenSSL::Cipher.new('aes-256-gcm').encrypt + cipher.key = key + + iv = cipher.random_iv + cipher.iv = iv + + encrypted_data = cipher.update(json_string) + cipher.final + auth_tag = cipher.auth_tag + + iv_b64 = Base64.strict_encode64(iv) + + ciphertext_with_tag = encrypted_data + auth_tag + ciphertext_b64 = Base64.strict_encode64(ciphertext_with_tag) + + encrypted_payload = { + "iv" => iv_b64, + "encryptedData" => ciphertext_b64 + } + output_payload_string = encrypted_payload.to_json + + escaped_output_payload_string = output_payload_string.gsub('"', '\\"') +%> + +{ + "Initial version": "<%= escaped_output_payload_string %>" +} \ No newline at end of file diff --git a/app-shell/src/config.js b/app-shell/src/config.js.erb similarity index 63% rename from app-shell/src/config.js rename to app-shell/src/config.js.erb index 7fa963e..13bd792 100644 --- a/app-shell/src/config.js +++ b/app-shell/src/config.js.erb @@ -1,13 +1,13 @@ - - +<% require 'digest/md5' %> +<% @app_admin_password = @schema['project']['@project_uuid'] ? @schema['project']['@project_uuid'].split('-').first : 'password' %> const config = { - admin_pass: "df170051", + admin_pass: "<%= @app_admin_password %>", admin_email: "admin@flatlogic.com", schema_encryption_key: process.env.SCHEMA_ENCRYPTION_KEY || '', - - project_uuid: 'df170051-a138-4641-ad0d-a5ac07a19601', +<% if @schema['project']['@project_uuid'] %> + project_uuid: '<%= @schema['project']['@project_uuid'] %>', flHost: process.env.NODE_ENV === 'production' ? 'https://flatlogic.com/projects' : 'http://localhost:3000/projects', - +<% end %> gitea_domain: process.env.GITEA_DOMAIN || 'gitea.flatlogic.app', gitea_username: process.env.GITEA_USERNAME || 'admin', gitea_api_token: process.env.GITEA_API_TOKEN || null, diff --git a/app-shell/src/index.js b/app-shell/src/index.js.erb similarity index 94% rename from app-shell/src/index.js rename to app-shell/src/index.js.erb index ee8436b..8ce97b4 100644 --- a/app-shell/src/index.js +++ b/app-shell/src/index.js.erb @@ -11,7 +11,7 @@ const vcsRoutes = require('./routes/vcs'); // Function to initialize the Git repository function initRepo() { - const projectId = '34916'; + const projectId = '<%= @schema['project']['project_id'] %>'; return VCS.initRepo(projectId); } @@ -51,4 +51,4 @@ startServer(); // Now perform Git check runGitCheck(); -module.exports = app; +module.exports = app; \ No newline at end of file diff --git a/backend/src/db/api/archival_items.js b/backend/src/db/api/archival_items.js index 974dab4..7ba88a5 100644 --- a/backend/src/db/api/archival_items.js +++ b/backend/src/db/api/archival_items.js @@ -19,8 +19,6 @@ module.exports = class Archival_itemsDBApi { description: data.description || null, isPublished: data.isPublished || false, - period_from: data.period_from || null, - period_to: data.period_to || null, importHash: data.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -57,8 +55,6 @@ module.exports = class Archival_itemsDBApi { description: item.description || null, isPublished: item.isPublished || false, - period_from: item.period_from || null, - period_to: item.period_to || null, importHash: item.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -108,11 +104,6 @@ module.exports = class Archival_itemsDBApi { if (data.isPublished !== undefined) updatePayload.isPublished = data.isPublished; - if (data.period_from !== undefined) - updatePayload.period_from = data.period_from; - - if (data.period_to !== undefined) updatePayload.period_to = data.period_to; - updatePayload.updatedById = currentUser.id; await archival_items.update(updatePayload, { transaction }); @@ -257,41 +248,6 @@ module.exports = class Archival_itemsDBApi { }; } - if (filter.period_to) { - where = { - ...where, - [Op.and]: Utils.ilike( - 'archival_items', - 'period_to', - filter.period_to, - ), - }; - } - - if (filter.period_fromRange) { - const [start, end] = filter.period_fromRange; - - if (start !== undefined && start !== null && start !== '') { - where = { - ...where, - period_from: { - ...where.period_from, - [Op.gte]: start, - }, - }; - } - - if (end !== undefined && end !== null && end !== '') { - where = { - ...where, - period_from: { - ...where.period_from, - [Op.lte]: end, - }, - }; - } - } - if (filter.active !== undefined) { where = { ...where, diff --git a/backend/src/db/api/galleries.js b/backend/src/db/api/periods.js similarity index 68% rename from backend/src/db/api/galleries.js rename to backend/src/db/api/periods.js index 9826976..4b45d53 100644 --- a/backend/src/db/api/galleries.js +++ b/backend/src/db/api/periods.js @@ -6,16 +6,18 @@ const Utils = require('../utils'); const Sequelize = db.Sequelize; const Op = Sequelize.Op; -module.exports = class GalleriesDBApi { +module.exports = class PeriodsDBApi { static async create(data, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const galleries = await db.galleries.create( + const periods = await db.periods.create( { id: data.id || undefined, name: data.name || null, + period_from: data.period_from || null, + period_to: data.period_to || null, importHash: data.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -23,21 +25,11 @@ module.exports = class GalleriesDBApi { { transaction }, ); - await galleries.setArchival_items(data.archival_items || [], { + await periods.setArchival_items(data.archival_items || [], { transaction, }); - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.galleries.getTableName(), - belongsToColumn: 'background_image', - belongsToId: galleries.id, - }, - data.background_image, - options, - ); - - return galleries; + return periods; } static async bulkImport(data, options) { @@ -45,10 +37,12 @@ module.exports = class GalleriesDBApi { const transaction = (options && options.transaction) || undefined; // Prepare data - wrapping individual data transformations in a map() method - const galleriesData = data.map((item, index) => ({ + const periodsData = data.map((item, index) => ({ id: item.id || undefined, name: item.name || null, + period_from: item.period_from || null, + period_to: item.period_to || null, importHash: item.importHash || null, createdById: currentUser.id, updatedById: currentUser.id, @@ -56,63 +50,44 @@ module.exports = class GalleriesDBApi { })); // Bulk create items - const galleries = await db.galleries.bulkCreate(galleriesData, { - transaction, - }); + const periods = await db.periods.bulkCreate(periodsData, { transaction }); // For each item created, replace relation files - for (let i = 0; i < galleries.length; i++) { - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.galleries.getTableName(), - belongsToColumn: 'background_image', - belongsToId: galleries[i].id, - }, - data[i].background_image, - options, - ); - } - - return galleries; + return periods; } static async update(id, data, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const galleries = await db.galleries.findByPk(id, {}, { transaction }); + const periods = await db.periods.findByPk(id, {}, { transaction }); const updatePayload = {}; if (data.name !== undefined) updatePayload.name = data.name; + if (data.period_from !== undefined) + updatePayload.period_from = data.period_from; + + if (data.period_to !== undefined) updatePayload.period_to = data.period_to; + updatePayload.updatedById = currentUser.id; - await galleries.update(updatePayload, { transaction }); + await periods.update(updatePayload, { transaction }); if (data.archival_items !== undefined) { - await galleries.setArchival_items(data.archival_items, { transaction }); + await periods.setArchival_items(data.archival_items, { transaction }); } - await FileDBApi.replaceRelationFiles( - { - belongsTo: db.galleries.getTableName(), - belongsToColumn: 'background_image', - belongsToId: galleries.id, - }, - data.background_image, - options, - ); - - return galleries; + return periods; } static async deleteByIds(ids, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const galleries = await db.galleries.findAll({ + const periods = await db.periods.findAll({ where: { id: { [Op.in]: ids, @@ -122,24 +97,24 @@ module.exports = class GalleriesDBApi { }); await db.sequelize.transaction(async (transaction) => { - for (const record of galleries) { + for (const record of periods) { await record.update({ deletedBy: currentUser.id }, { transaction }); } - for (const record of galleries) { + for (const record of periods) { await record.destroy({ transaction }); } }); - return galleries; + return periods; } static async remove(id, options) { const currentUser = (options && options.currentUser) || { id: null }; const transaction = (options && options.transaction) || undefined; - const galleries = await db.galleries.findByPk(id, options); + const periods = await db.periods.findByPk(id, options); - await galleries.update( + await periods.update( { deletedBy: currentUser.id, }, @@ -148,29 +123,25 @@ module.exports = class GalleriesDBApi { }, ); - await galleries.destroy({ + await periods.destroy({ transaction, }); - return galleries; + return periods; } static async findBy(where, options) { const transaction = (options && options.transaction) || undefined; - const galleries = await db.galleries.findOne({ where }, { transaction }); + const periods = await db.periods.findOne({ where }, { transaction }); - if (!galleries) { - return galleries; + if (!periods) { + return periods; } - const output = galleries.get({ plain: true }); + const output = periods.get({ plain: true }); - output.archival_items = await galleries.getArchival_items({ - transaction, - }); - - output.background_image = await galleries.getBackground_image({ + output.archival_items = await periods.getArchival_items({ transaction, }); @@ -195,11 +166,6 @@ module.exports = class GalleriesDBApi { as: 'archival_items', required: false, }, - - { - model: db.file, - as: 'background_image', - }, ]; if (filter) { @@ -213,10 +179,58 @@ module.exports = class GalleriesDBApi { if (filter.name) { where = { ...where, - [Op.and]: Utils.ilike('galleries', 'name', filter.name), + [Op.and]: Utils.ilike('periods', 'name', filter.name), }; } + if (filter.period_fromRange) { + const [start, end] = filter.period_fromRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + period_from: { + ...where.period_from, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + period_from: { + ...where.period_from, + [Op.lte]: end, + }, + }; + } + } + + if (filter.period_toRange) { + const [start, end] = filter.period_toRange; + + if (start !== undefined && start !== null && start !== '') { + where = { + ...where, + period_to: { + ...where.period_to, + [Op.gte]: start, + }, + }; + } + + if (end !== undefined && end !== null && end !== '') { + where = { + ...where, + period_to: { + ...where.period_to, + [Op.lte]: end, + }, + }; + } + } + if (filter.active !== undefined) { where = { ...where, @@ -299,7 +313,7 @@ module.exports = class GalleriesDBApi { } try { - const { rows, count } = await db.galleries.findAndCountAll(queryOptions); + const { rows, count } = await db.periods.findAndCountAll(queryOptions); return { rows: options?.countOnly ? [] : rows, @@ -318,12 +332,12 @@ module.exports = class GalleriesDBApi { where = { [Op.or]: [ { ['id']: Utils.uuid(query) }, - Utils.ilike('galleries', 'id', query), + Utils.ilike('periods', 'id', query), ], }; } - const records = await db.galleries.findAll({ + const records = await db.periods.findAll({ attributes: ['id', 'id'], where, limit: limit ? Number(limit) : undefined, diff --git a/backend/src/db/migrations/1760597839174.js b/backend/src/db/migrations/1760597839174.js new file mode 100644 index 0000000..6747125 --- /dev/null +++ b/backend/src/db/migrations/1760597839174.js @@ -0,0 +1,92 @@ +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.renameTable('galleries', 'periods', { transaction }); + + await queryInterface.addColumn( + 'periods', + 'period_from', + { + type: Sequelize.DataTypes.DATE, + }, + { transaction }, + ); + + await queryInterface.addColumn( + 'periods', + 'period_to', + { + type: Sequelize.DataTypes.DATE, + }, + { transaction }, + ); + + await queryInterface.removeColumn('archival_items', 'period_from', { + transaction, + }); + + await queryInterface.removeColumn('archival_items', 'period_to', { + 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.addColumn( + 'archival_items', + 'period_to', + { + type: Sequelize.DataTypes.TEXT, + }, + { transaction }, + ); + + await queryInterface.addColumn( + 'archival_items', + 'period_from', + { + type: Sequelize.DataTypes.DATEONLY, + }, + { transaction }, + ); + + await queryInterface.removeColumn('periods', 'period_to', { + transaction, + }); + + await queryInterface.removeColumn('periods', 'period_from', { + transaction, + }); + + await queryInterface.renameTable('periods', 'galleries', { transaction }); + + await transaction.commit(); + } catch (err) { + await transaction.rollback(); + throw err; + } + }, +}; diff --git a/backend/src/db/models/archival_items.js b/backend/src/db/models/archival_items.js index 05f45bd..86aa2d2 100644 --- a/backend/src/db/models/archival_items.js +++ b/backend/src/db/models/archival_items.js @@ -29,20 +29,6 @@ module.exports = function (sequelize, DataTypes) { defaultValue: false, }, - period_from: { - type: DataTypes.DATEONLY, - - get: function () { - return this.getDataValue('period_from') - ? moment.utc(this.getDataValue('period_from')).format('YYYY-MM-DD') - : null; - }, - }, - - period_to: { - type: DataTypes.TEXT, - }, - importHash: { type: DataTypes.STRING(255), allowNull: true, diff --git a/backend/src/db/models/galleries.js b/backend/src/db/models/periods.js similarity index 58% rename from backend/src/db/models/galleries.js rename to backend/src/db/models/periods.js index 87fc62b..2bb0a98 100644 --- a/backend/src/db/models/galleries.js +++ b/backend/src/db/models/periods.js @@ -5,8 +5,8 @@ const bcrypt = require('bcrypt'); const moment = require('moment'); module.exports = function (sequelize, DataTypes) { - const galleries = sequelize.define( - 'galleries', + const periods = sequelize.define( + 'periods', { id: { type: DataTypes.UUID, @@ -18,6 +18,14 @@ module.exports = function (sequelize, DataTypes) { type: DataTypes.TEXT, }, + period_from: { + type: DataTypes.DATE, + }, + + period_to: { + type: DataTypes.DATE, + }, + importHash: { type: DataTypes.STRING(255), allowNull: true, @@ -31,47 +39,37 @@ module.exports = function (sequelize, DataTypes) { }, ); - galleries.associate = (db) => { - db.galleries.belongsToMany(db.archival_items, { + periods.associate = (db) => { + db.periods.belongsToMany(db.archival_items, { as: 'archival_items', foreignKey: { - name: 'galleries_archival_itemsId', + name: 'periods_archival_itemsId', }, constraints: false, - through: 'galleriesArchival_itemsArchival_items', + through: 'periodsArchival_itemsArchival_items', }); - db.galleries.belongsToMany(db.archival_items, { + db.periods.belongsToMany(db.archival_items, { as: 'archival_items_filter', foreignKey: { - name: 'galleries_archival_itemsId', + name: 'periods_archival_itemsId', }, constraints: false, - through: 'galleriesArchival_itemsArchival_items', + through: 'periodsArchival_itemsArchival_items', }); /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity //end loop - db.galleries.hasMany(db.file, { - as: 'background_image', - foreignKey: 'belongsToId', - constraints: false, - scope: { - belongsTo: db.galleries.getTableName(), - belongsToColumn: 'background_image', - }, - }); - - db.galleries.belongsTo(db.users, { + db.periods.belongsTo(db.users, { as: 'createdBy', }); - db.galleries.belongsTo(db.users, { + db.periods.belongsTo(db.users, { as: 'updatedBy', }); }; - return galleries; + return periods; }; diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js index bc6bf32..c7f465d 100644 --- a/backend/src/db/seeders/20200430130760-user-roles.js +++ b/backend/src/db/seeders/20200430130760-user-roles.js @@ -72,7 +72,7 @@ module.exports = { const entities = [ 'users', 'archival_items', - 'galleries', + 'periods', 'tags', 'roles', 'permissions', @@ -164,25 +164,25 @@ primary key ("roles_permissionsId", "permissionId") createdAt, updatedAt, roles_permissionsId: getId('User'), - permissionId: getId('CREATE_GALLERIES'), + permissionId: getId('CREATE_PERIODS'), }, { createdAt, updatedAt, roles_permissionsId: getId('User'), - permissionId: getId('READ_GALLERIES'), + permissionId: getId('READ_PERIODS'), }, { createdAt, updatedAt, roles_permissionsId: getId('User'), - permissionId: getId('UPDATE_GALLERIES'), + permissionId: getId('UPDATE_PERIODS'), }, { createdAt, updatedAt, roles_permissionsId: getId('User'), - permissionId: getId('DELETE_GALLERIES'), + permissionId: getId('DELETE_PERIODS'), }, { @@ -271,25 +271,25 @@ primary key ("roles_permissionsId", "permissionId") createdAt, updatedAt, roles_permissionsId: getId('Administrator'), - permissionId: getId('CREATE_GALLERIES'), + permissionId: getId('CREATE_PERIODS'), }, { createdAt, updatedAt, roles_permissionsId: getId('Administrator'), - permissionId: getId('READ_GALLERIES'), + permissionId: getId('READ_PERIODS'), }, { createdAt, updatedAt, roles_permissionsId: getId('Administrator'), - permissionId: getId('UPDATE_GALLERIES'), + permissionId: getId('UPDATE_PERIODS'), }, { createdAt, updatedAt, roles_permissionsId: getId('Administrator'), - permissionId: getId('DELETE_GALLERIES'), + permissionId: getId('DELETE_PERIODS'), }, { diff --git a/backend/src/db/seeders/20251016065719.js b/backend/src/db/seeders/20251016065719.js new file mode 100644 index 0000000..75d90f5 --- /dev/null +++ b/backend/src/db/seeders/20251016065719.js @@ -0,0 +1,103 @@ +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 = ['periods']; + + const previousValues = ['galleries']; + const createdPreviousPermissions = + previousValues.flatMap(createPermissions); + const namesPreviousPermissions = createdPreviousPermissions.map( + (p) => p.name, + ); + + 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); + } + + // Remove previous permissions + await db.permissions.destroy({ + where: { + name: { + [Sequelize.Op.in]: namesPreviousPermissions, + }, + }, + }); + }, + down: async (queryInterface, Sequelize) => { + await queryInterface.bulkDelete( + 'permissions', + entities.flatMap(createPermissions), + ); + }, +}; diff --git a/backend/src/index.js b/backend/src/index.js index f8a0f3a..e505e95 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -21,7 +21,7 @@ const usersRoutes = require('./routes/users'); const archival_itemsRoutes = require('./routes/archival_items'); -const galleriesRoutes = require('./routes/galleries'); +const periodsRoutes = require('./routes/periods'); const tagsRoutes = require('./routes/tags'); @@ -107,9 +107,9 @@ app.use( ); app.use( - '/api/galleries', + '/api/periods', passport.authenticate('jwt', { session: false }), - galleriesRoutes, + periodsRoutes, ); app.use( diff --git a/backend/src/routes/archival_items.js b/backend/src/routes/archival_items.js index 54216a0..cf66bd1 100644 --- a/backend/src/routes/archival_items.js +++ b/backend/src/routes/archival_items.js @@ -26,9 +26,6 @@ router.use(checkCrudPermissions('archival_items')); * description: * type: string * default: description - * period_to: - * type: string - * default: period_to */ @@ -316,7 +313,7 @@ router.get( currentUser, }); if (filetype && filetype === 'csv') { - const fields = ['id', 'label', 'description', 'period_to', 'period_from']; + const fields = ['id', 'label', 'description']; const opts = { fields }; try { const csv = parse(payload.rows, opts); diff --git a/backend/src/routes/galleries.js b/backend/src/routes/periods.js similarity index 78% rename from backend/src/routes/galleries.js rename to backend/src/routes/periods.js index 84a52bc..5c1c10c 100644 --- a/backend/src/routes/galleries.js +++ b/backend/src/routes/periods.js @@ -1,7 +1,7 @@ const express = require('express'); -const GalleriesService = require('../services/galleries'); -const GalleriesDBApi = require('../db/api/galleries'); +const PeriodsService = require('../services/periods'); +const PeriodsDBApi = require('../db/api/periods'); const wrapAsync = require('../helpers').wrapAsync; const router = express.Router(); @@ -10,13 +10,13 @@ const { parse } = require('json2csv'); const { checkCrudPermissions } = require('../middlewares/check-permissions'); -router.use(checkCrudPermissions('galleries')); +router.use(checkCrudPermissions('periods')); /** * @swagger * components: * schemas: - * Galleries: + * Periods: * type: object * properties: @@ -29,17 +29,17 @@ router.use(checkCrudPermissions('galleries')); /** * @swagger * tags: - * name: Galleries - * description: The Galleries managing API + * name: Periods + * description: The Periods managing API */ /** * @swagger - * /api/galleries: + * /api/periods: * post: * security: * - bearerAuth: [] - * tags: [Galleries] + * tags: [Periods] * summary: Add new item * description: Add new item * requestBody: @@ -51,14 +51,14 @@ router.use(checkCrudPermissions('galleries')); * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * responses: * 200: * description: The item was successfully added * content: * application/json: * schema: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -73,7 +73,7 @@ router.post( req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await GalleriesService.create( + await PeriodsService.create( req.body.data, req.currentUser, true, @@ -90,7 +90,7 @@ router.post( * post: * security: * - bearerAuth: [] - * tags: [Galleries] + * tags: [Periods] * summary: Bulk import items * description: Bulk import items * requestBody: @@ -103,14 +103,14 @@ router.post( * description: Data of the updated items * type: array * items: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * responses: * 200: * description: The items were successfully imported * content: * application/json: * schema: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 401: * $ref: "#/components/responses/UnauthorizedError" * 405: @@ -126,7 +126,7 @@ router.post( req.headers.referer || `${req.protocol}://${req.hostname}${req.originalUrl}`; const link = new URL(referer); - await GalleriesService.bulkImport(req, res, true, link.host); + await PeriodsService.bulkImport(req, res, true, link.host); const payload = true; res.status(200).send(payload); }), @@ -134,11 +134,11 @@ router.post( /** * @swagger - * /api/galleries/{id}: + * /api/periods/{id}: * put: * security: * - bearerAuth: [] - * tags: [Galleries] + * tags: [Periods] * summary: Update the data of the selected item * description: Update the data of the selected item * parameters: @@ -161,7 +161,7 @@ router.post( * data: * description: Data of the updated item * type: object - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * required: * - id * responses: @@ -170,7 +170,7 @@ router.post( * content: * application/json: * schema: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 400: * description: Invalid ID supplied * 401: @@ -183,7 +183,7 @@ router.post( router.put( '/:id', wrapAsync(async (req, res) => { - await GalleriesService.update(req.body.data, req.body.id, req.currentUser); + await PeriodsService.update(req.body.data, req.body.id, req.currentUser); const payload = true; res.status(200).send(payload); }), @@ -191,11 +191,11 @@ router.put( /** * @swagger - * /api/galleries/{id}: + * /api/periods/{id}: * delete: * security: * - bearerAuth: [] - * tags: [Galleries] + * tags: [Periods] * summary: Delete the selected item * description: Delete the selected item * parameters: @@ -211,7 +211,7 @@ router.put( * content: * application/json: * schema: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 400: * description: Invalid ID supplied * 401: @@ -224,7 +224,7 @@ router.put( router.delete( '/:id', wrapAsync(async (req, res) => { - await GalleriesService.remove(req.params.id, req.currentUser); + await PeriodsService.remove(req.params.id, req.currentUser); const payload = true; res.status(200).send(payload); }), @@ -232,11 +232,11 @@ router.delete( /** * @swagger - * /api/galleries/deleteByIds: + * /api/periods/deleteByIds: * post: * security: * - bearerAuth: [] - * tags: [Galleries] + * tags: [Periods] * summary: Delete the selected item list * description: Delete the selected item list * requestBody: @@ -254,7 +254,7 @@ router.delete( * content: * application/json: * schema: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -265,7 +265,7 @@ router.delete( router.post( '/deleteByIds', wrapAsync(async (req, res) => { - await GalleriesService.deleteByIds(req.body.data, req.currentUser); + await PeriodsService.deleteByIds(req.body.data, req.currentUser); const payload = true; res.status(200).send(payload); }), @@ -273,22 +273,22 @@ router.post( /** * @swagger - * /api/galleries: + * /api/periods: * get: * security: * - bearerAuth: [] - * tags: [Galleries] - * summary: Get all galleries - * description: Get all galleries + * tags: [Periods] + * summary: Get all periods + * description: Get all periods * responses: * 200: - * description: Galleries list successfully received + * description: Periods list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -302,9 +302,9 @@ router.get( const filetype = req.query.filetype; const currentUser = req.currentUser; - const payload = await GalleriesDBApi.findAll(req.query, { currentUser }); + const payload = await PeriodsDBApi.findAll(req.query, { currentUser }); if (filetype && filetype === 'csv') { - const fields = ['id', 'name']; + const fields = ['id', 'name', 'period_from', 'period_to']; const opts = { fields }; try { const csv = parse(payload.rows, opts); @@ -321,22 +321,22 @@ router.get( /** * @swagger - * /api/galleries/count: + * /api/periods/count: * get: * security: * - bearerAuth: [] - * tags: [Galleries] - * summary: Count all galleries - * description: Count all galleries + * tags: [Periods] + * summary: Count all periods + * description: Count all periods * responses: * 200: - * description: Galleries count successfully received + * description: Periods count successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -348,7 +348,7 @@ router.get( '/count', wrapAsync(async (req, res) => { const currentUser = req.currentUser; - const payload = await GalleriesDBApi.findAll(req.query, null, { + const payload = await PeriodsDBApi.findAll(req.query, null, { countOnly: true, currentUser, }); @@ -359,22 +359,22 @@ router.get( /** * @swagger - * /api/galleries/autocomplete: + * /api/periods/autocomplete: * get: * security: * - bearerAuth: [] - * tags: [Galleries] - * summary: Find all galleries that match search criteria - * description: Find all galleries that match search criteria + * tags: [Periods] + * summary: Find all periods that match search criteria + * description: Find all periods that match search criteria * responses: * 200: - * description: Galleries list successfully received + * description: Periods list successfully received * content: * application/json: * schema: * type: array * items: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 401: * $ref: "#/components/responses/UnauthorizedError" * 404: @@ -383,7 +383,7 @@ router.get( * description: Some server error */ router.get('/autocomplete', async (req, res) => { - const payload = await GalleriesDBApi.findAllAutocomplete( + const payload = await PeriodsDBApi.findAllAutocomplete( req.query.query, req.query.limit, req.query.offset, @@ -394,11 +394,11 @@ router.get('/autocomplete', async (req, res) => { /** * @swagger - * /api/galleries/{id}: + * /api/periods/{id}: * get: * security: * - bearerAuth: [] - * tags: [Galleries] + * tags: [Periods] * summary: Get selected item * description: Get selected item * parameters: @@ -414,7 +414,7 @@ router.get('/autocomplete', async (req, res) => { * content: * application/json: * schema: - * $ref: "#/components/schemas/Galleries" + * $ref: "#/components/schemas/Periods" * 400: * description: Invalid ID supplied * 401: @@ -427,7 +427,7 @@ router.get('/autocomplete', async (req, res) => { router.get( '/:id', wrapAsync(async (req, res) => { - const payload = await GalleriesDBApi.findBy({ id: req.params.id }); + const payload = await PeriodsDBApi.findBy({ id: req.params.id }); res.status(200).send(payload); }), diff --git a/backend/src/services/galleries.js b/backend/src/services/periods.js similarity index 81% rename from backend/src/services/galleries.js rename to backend/src/services/periods.js index 6290397..82a75ce 100644 --- a/backend/src/services/galleries.js +++ b/backend/src/services/periods.js @@ -1,5 +1,5 @@ const db = require('../db/models'); -const GalleriesDBApi = require('../db/api/galleries'); +const PeriodsDBApi = require('../db/api/periods'); const processFile = require('../middlewares/upload'); const ValidationError = require('./notifications/errors/validation'); const csv = require('csv-parser'); @@ -7,11 +7,11 @@ const axios = require('axios'); const config = require('../config'); const stream = require('stream'); -module.exports = class GalleriesService { +module.exports = class PeriodsService { static async create(data, currentUser) { const transaction = await db.sequelize.transaction(); try { - await GalleriesDBApi.create(data, { + await PeriodsDBApi.create(data, { currentUser, transaction, }); @@ -44,7 +44,7 @@ module.exports = class GalleriesService { .on('error', (error) => reject(error)); }); - await GalleriesDBApi.bulkImport(results, { + await PeriodsDBApi.bulkImport(results, { transaction, ignoreDuplicates: true, validate: true, @@ -61,19 +61,19 @@ module.exports = class GalleriesService { static async update(data, id, currentUser) { const transaction = await db.sequelize.transaction(); try { - let galleries = await GalleriesDBApi.findBy({ id }, { transaction }); + let periods = await PeriodsDBApi.findBy({ id }, { transaction }); - if (!galleries) { - throw new ValidationError('galleriesNotFound'); + if (!periods) { + throw new ValidationError('periodsNotFound'); } - const updatedGalleries = await GalleriesDBApi.update(id, data, { + const updatedPeriods = await PeriodsDBApi.update(id, data, { currentUser, transaction, }); await transaction.commit(); - return updatedGalleries; + return updatedPeriods; } catch (error) { await transaction.rollback(); throw error; @@ -84,7 +84,7 @@ module.exports = class GalleriesService { const transaction = await db.sequelize.transaction(); try { - await GalleriesDBApi.deleteByIds(ids, { + await PeriodsDBApi.deleteByIds(ids, { currentUser, transaction, }); @@ -100,7 +100,7 @@ module.exports = class GalleriesService { const transaction = await db.sequelize.transaction(); try { - await GalleriesDBApi.remove(id, { + await PeriodsDBApi.remove(id, { currentUser, transaction, }); diff --git a/backend/src/services/search.js b/backend/src/services/search.js index d1fb784..f6eea70 100644 --- a/backend/src/services/search.js +++ b/backend/src/services/search.js @@ -43,9 +43,9 @@ module.exports = class SearchService { const tableColumns = { users: ['firstName', 'lastName', 'phoneNumber', 'email'], - archival_items: ['label', 'description', 'period_to'], + archival_items: ['label', 'description'], - galleries: ['name'], + periods: ['name'], tags: ['name'], }; diff --git a/frontend/src/components/Archival_items/CardArchival_items.tsx b/frontend/src/components/Archival_items/CardArchival_items.tsx index faba572..1fadef3 100644 --- a/frontend/src/components/Archival_items/CardArchival_items.tsx +++ b/frontend/src/components/Archival_items/CardArchival_items.tsx @@ -127,28 +127,6 @@ const CardArchival_items = ({ -
-
- Period From -
-
-
- {dataFormatter.dateFormatter(item.period_from)} -
-
-
- -
-
- Period To -
-
-
- {item.period_to} -
-
-
-
Tags
diff --git a/frontend/src/components/Archival_items/ListArchival_items.tsx b/frontend/src/components/Archival_items/ListArchival_items.tsx index 269ccc4..42fff3e 100644 --- a/frontend/src/components/Archival_items/ListArchival_items.tsx +++ b/frontend/src/components/Archival_items/ListArchival_items.tsx @@ -89,18 +89,6 @@ const ListArchival_items = ({

-
-

Period From

-

- {dataFormatter.dateFormatter(item.period_from)} -

-
- -
-

Period To

-

{item.period_to}

-
-

Tags

diff --git a/frontend/src/components/Archival_items/configureArchival_itemsCols.tsx b/frontend/src/components/Archival_items/configureArchival_itemsCols.tsx index 9b5ad56..dc93b6b 100644 --- a/frontend/src/components/Archival_items/configureArchival_itemsCols.tsx +++ b/frontend/src/components/Archival_items/configureArchival_itemsCols.tsx @@ -96,34 +96,6 @@ export const loadColumns = async ( type: 'boolean', }, - { - field: 'period_from', - headerName: 'Period From', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - - type: 'date', - valueGetter: (params: GridValueGetterParams) => - new Date(params.row.period_from), - }, - - { - field: 'period_to', - headerName: 'Period To', - flex: 1, - minWidth: 120, - filterable: false, - headerClassName: 'datagrid--header', - cellClassName: 'datagrid--cell', - - editable: hasUpdatePermission, - }, - { field: 'tags', headerName: 'Tags', diff --git a/frontend/src/components/Galleries/CardGalleries.tsx b/frontend/src/components/Periods/CardPeriods.tsx similarity index 73% rename from frontend/src/components/Galleries/CardGalleries.tsx rename to frontend/src/components/Periods/CardPeriods.tsx index 6d57860..09aa4be 100644 --- a/frontend/src/components/Galleries/CardGalleries.tsx +++ b/frontend/src/components/Periods/CardPeriods.tsx @@ -11,7 +11,7 @@ import Link from 'next/link'; import { hasPermission } from '../../helpers/userPermissions'; type Props = { - galleries: any[]; + periods: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -19,8 +19,8 @@ type Props = { onPageChange: (page: number) => void; }; -const CardGalleries = ({ - galleries, +const CardPeriods = ({ + periods, loading, onDelete, currentPage, @@ -36,7 +36,7 @@ const CardGalleries = ({ const focusRing = useAppSelector((state) => state.style.focusRingColor); const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_GALLERIES'); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERIODS'); return (

@@ -46,7 +46,7 @@ const CardGalleries = ({ className='grid grid-cols-1 gap-x-6 gap-y-8 lg:grid-cols-3 2xl:grid-cols-4 xl:gap-x-8' > {!loading && - galleries.map((item, index) => ( + periods.map((item, index) => (
  • - -

    {item.id}

    + {item.id} -
    +
    @@ -104,22 +98,29 @@ const CardGalleries = ({
    - Background Image + Period From
    -
    - +
    + {dataFormatter.dateTimeFormatter(item.period_from)} +
    +
    +
    + +
    +
    + Period To +
    +
    +
    + {dataFormatter.dateTimeFormatter(item.period_to)}
  • ))} - {!loading && galleries.length === 0 && ( + {!loading && periods.length === 0 && (

    No data to display

    @@ -136,4 +137,4 @@ const CardGalleries = ({ ); }; -export default CardGalleries; +export default CardPeriods; diff --git a/frontend/src/components/Galleries/ListGalleries.tsx b/frontend/src/components/Periods/ListPeriods.tsx similarity index 76% rename from frontend/src/components/Galleries/ListGalleries.tsx rename to frontend/src/components/Periods/ListPeriods.tsx index 3d4a6cf..85bc7d7 100644 --- a/frontend/src/components/Galleries/ListGalleries.tsx +++ b/frontend/src/components/Periods/ListPeriods.tsx @@ -12,7 +12,7 @@ import Link from 'next/link'; import { hasPermission } from '../../helpers/userPermissions'; type Props = { - galleries: any[]; + periods: any[]; loading: boolean; onDelete: (id: string) => void; currentPage: number; @@ -20,8 +20,8 @@ type Props = { onPageChange: (page: number) => void; }; -const ListGalleries = ({ - galleries, +const ListPeriods = ({ + periods, loading, onDelete, currentPage, @@ -29,7 +29,7 @@ const ListGalleries = ({ onPageChange, }: Props) => { const currentUser = useAppSelector((state) => state.auth.currentUser); - const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_GALLERIES'); + const hasUpdatePermission = hasPermission(currentUser, 'UPDATE_PERIODS'); const corners = useAppSelector((state) => state.style.corners); const bgColor = useAppSelector((state) => state.style.cardsColor); @@ -39,23 +39,14 @@ const ListGalleries = ({
    {loading && } {!loading && - galleries.map((item) => ( + periods.map((item) => (
    - - dark:divide-dark-700 overflow-x-auto' } @@ -77,28 +68,31 @@ const ListGalleries = ({
    -

    - Background Image +

    Period From

    +

    + {dataFormatter.dateTimeFormatter(item.period_from)} +

    +
    + +
    +

    Period To

    +

    + {dataFormatter.dateTimeFormatter(item.period_to)}

    -
    ))} - {!loading && galleries.length === 0 && ( + {!loading && periods.length === 0 && (

    No data to display

    @@ -115,4 +109,4 @@ const ListGalleries = ({ ); }; -export default ListGalleries; +export default ListPeriods; diff --git a/frontend/src/components/Galleries/TableGalleries.tsx b/frontend/src/components/Periods/TablePeriods.tsx similarity index 96% rename from frontend/src/components/Galleries/TableGalleries.tsx rename to frontend/src/components/Periods/TablePeriods.tsx index 376a751..a18e7e2 100644 --- a/frontend/src/components/Galleries/TableGalleries.tsx +++ b/frontend/src/components/Periods/TablePeriods.tsx @@ -10,19 +10,19 @@ import { deleteItem, setRefetch, deleteItemsByIds, -} from '../../stores/galleries/galleriesSlice'; +} from '../../stores/periods/periodsSlice'; 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 './configureGalleriesCols'; +import { loadColumns } from './configurePeriodsCols'; import _ from 'lodash'; import dataFormatter from '../../helpers/dataFormatter'; import { dataGridStyles } from '../../styles'; const perPage = 10; -const TableSampleGalleries = ({ +const TableSamplePeriods = ({ filterItems, setFilterItems, filters, @@ -47,12 +47,12 @@ const TableSampleGalleries = ({ ]); const { - galleries, + periods, loading, count, - notify: galleriesNotify, + notify: periodsNotify, refetch, - } = useAppSelector((state) => state.galleries); + } = useAppSelector((state) => state.periods); const { currentUser } = useAppSelector((state) => state.auth); const focusRing = useAppSelector((state) => state.style.focusRingColor); const bgColor = useAppSelector((state) => state.style.bgLayoutColor); @@ -73,13 +73,10 @@ const TableSampleGalleries = ({ }; useEffect(() => { - if (galleriesNotify.showNotification) { - notify( - galleriesNotify.typeNotification, - galleriesNotify.textNotification, - ); + if (periodsNotify.showNotification) { + notify(periodsNotify.typeNotification, periodsNotify.textNotification); } - }, [galleriesNotify.showNotification]); + }, [periodsNotify.showNotification]); useEffect(() => { if (!currentUser) return; @@ -184,7 +181,7 @@ const TableSampleGalleries = ({ useEffect(() => { if (!currentUser) return; - loadColumns(handleDeleteModalAction, `galleries`, currentUser).then( + loadColumns(handleDeleteModalAction, `periods`, currentUser).then( (newCols) => setColumns(newCols), ); }, [currentUser]); @@ -218,7 +215,7 @@ const TableSampleGalleries = ({ sx={dataGridStyles} className={'datagrid--table'} getRowClassName={() => `datagrid--row`} - rows={galleries ?? []} + rows={periods ?? []} columns={columns} initialState={{ pagination: { @@ -481,4 +478,4 @@ const TableSampleGalleries = ({ ); }; -export default TableSampleGalleries; +export default TableSamplePeriods; diff --git a/frontend/src/components/Galleries/configureGalleriesCols.tsx b/frontend/src/components/Periods/configurePeriodsCols.tsx similarity index 75% rename from frontend/src/components/Galleries/configureGalleriesCols.tsx rename to frontend/src/components/Periods/configurePeriodsCols.tsx index 1496f43..7a781a5 100644 --- a/frontend/src/components/Galleries/configureGalleriesCols.tsx +++ b/frontend/src/components/Periods/configurePeriodsCols.tsx @@ -35,7 +35,7 @@ export const loadColumns = async ( } } - const hasUpdatePermission = hasPermission(user, 'UPDATE_GALLERIES'); + const hasUpdatePermission = hasPermission(user, 'UPDATE_PERIODS'); return [ { @@ -70,23 +70,35 @@ export const loadColumns = async ( }, { - field: 'background_image', - headerName: 'Background Image', + field: 'period_from', + headerName: 'Period From', flex: 1, minWidth: 120, filterable: false, headerClassName: 'datagrid--header', cellClassName: 'datagrid--cell', - editable: false, - sortable: false, - renderCell: (params: GridValueGetterParams) => ( - - ), + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.period_from), + }, + + { + field: 'period_to', + headerName: 'Period To', + flex: 1, + minWidth: 120, + filterable: false, + headerClassName: 'datagrid--header', + cellClassName: 'datagrid--cell', + + editable: hasUpdatePermission, + + type: 'dateTime', + valueGetter: (params: GridValueGetterParams) => + new Date(params.row.period_to), }, { @@ -101,8 +113,8 @@ export const loadColumns = async (
    , diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts index add22be..ee81ad2 100644 --- a/frontend/src/menuAside.ts +++ b/frontend/src/menuAside.ts @@ -25,12 +25,12 @@ const menuAside: MenuAsideItem[] = [ permissions: 'READ_ARCHIVAL_ITEMS', }, { - href: '/galleries/galleries-list', - label: 'Galleries', + href: '/periods/periods-list', + label: 'Periods', // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore icon: icon.mdiTable ?? icon.mdiTable, - permissions: 'READ_GALLERIES', + permissions: 'READ_PERIODS', }, { href: '/tags/tags-list', diff --git a/frontend/src/pages/archival_items/[archival_itemsId].tsx b/frontend/src/pages/archival_items/[archival_itemsId].tsx index 1ca33e2..5390710 100644 --- a/frontend/src/pages/archival_items/[archival_itemsId].tsx +++ b/frontend/src/pages/archival_items/[archival_itemsId].tsx @@ -44,10 +44,6 @@ const EditArchival_items = () => { isPublished: false, - period_from: new Date(), - - period_to: '', - tags: [], }; const [initialValues, setInitialValues] = useState(initVals); @@ -135,28 +131,6 @@ const EditArchival_items = () => { > - - - setInitialValues({ ...initialValues, period_from: date }) - } - /> - - - - - - { isPublished: false, - period_from: new Date(), - - period_to: '', - tags: [], }; const [initialValues, setInitialValues] = useState(initVals); @@ -133,28 +129,6 @@ const EditArchival_itemsPage = () => { > - - - setInitialValues({ ...initialValues, period_from: date }) - } - /> - - - - - - { const [filters] = useState([ { label: 'Label', title: 'label' }, { label: 'Description', title: 'description' }, - { label: 'Period To', title: 'period_to' }, { label: 'Tags', title: 'tags' }, ]); diff --git a/frontend/src/pages/archival_items/archival_items-new.tsx b/frontend/src/pages/archival_items/archival_items-new.tsx index 3c3f20b..2e8a30b 100644 --- a/frontend/src/pages/archival_items/archival_items-new.tsx +++ b/frontend/src/pages/archival_items/archival_items-new.tsx @@ -41,11 +41,6 @@ const initialValues = { isPublished: false, - period_from: '', - datePeriod_from: '', - - period_to: '', - tags: [], }; @@ -108,18 +103,6 @@ const Archival_itemsNew = () => { > - - - - - - - - { const [filters] = useState([ { label: 'Label', title: 'label' }, { label: 'Description', title: 'description' }, - { label: 'Period To', title: 'period_to' }, { label: 'Tags', title: 'tags' }, ]); diff --git a/frontend/src/pages/archival_items/archival_items-view.tsx b/frontend/src/pages/archival_items/archival_items-view.tsx index c26a67b..1b0d9c6 100644 --- a/frontend/src/pages/archival_items/archival_items-view.tsx +++ b/frontend/src/pages/archival_items/archival_items-view.tsx @@ -88,32 +88,6 @@ const Archival_itemsView = () => { /> - - {archival_items.period_from ? ( - - ) : ( -

    No Period From

    - )} -
    - -
    -

    Period To

    -

    {archival_items?.period_to}

    -
    - <>

    Tags

    { const [users, setUsers] = React.useState(loadingMessage); const [archival_items, setArchival_items] = React.useState(loadingMessage); - const [galleries, setGalleries] = React.useState(loadingMessage); + const [periods, setPeriods] = React.useState(loadingMessage); const [tags, setTags] = React.useState(loadingMessage); const [roles, setRoles] = React.useState(loadingMessage); const [permissions, setPermissions] = React.useState(loadingMessage); @@ -47,7 +47,7 @@ const Dashboard = () => { const entities = [ 'users', 'archival_items', - 'galleries', + 'periods', 'tags', 'roles', 'permissions', @@ -55,7 +55,7 @@ const Dashboard = () => { const fns = [ setUsers, setArchival_items, - setGalleries, + setPeriods, setTags, setRoles, setPermissions, @@ -235,8 +235,8 @@ const Dashboard = () => { )} - {hasPermission(currentUser, 'READ_GALLERIES') && ( - + {hasPermission(currentUser, 'READ_PERIODS') && ( +
    {
    - Galleries + Periods
    - {galleries} + {periods}
    diff --git a/frontend/src/pages/galleries/[galleriesId].tsx b/frontend/src/pages/periods/[periodsId].tsx similarity index 61% rename from frontend/src/pages/galleries/[galleriesId].tsx rename to frontend/src/pages/periods/[periodsId].tsx index 2a8c7e3..e68c1f2 100644 --- a/frontend/src/pages/galleries/[galleriesId].tsx +++ b/frontend/src/pages/periods/[periodsId].tsx @@ -25,14 +25,14 @@ import { SelectFieldMany } from '../../components/SelectFieldMany'; import { SwitchField } from '../../components/SwitchField'; import { RichTextField } from '../../components/RichTextField'; -import { update, fetch } from '../../stores/galleries/galleriesSlice'; +import { update, fetch } from '../../stores/periods/periodsSlice'; 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 EditGalleries = () => { +const EditPeriods = () => { const router = useRouter(); const dispatch = useAppDispatch(); const initVals = { @@ -40,50 +40,50 @@ const EditGalleries = () => { archival_items: [], - background_image: [], + period_from: new Date(), + + period_to: new Date(), }; const [initialValues, setInitialValues] = useState(initVals); - const { galleries } = useAppSelector((state) => state.galleries); + const { periods } = useAppSelector((state) => state.periods); - const { galleriesId } = router.query; + const { periodsId } = router.query; useEffect(() => { - dispatch(fetch({ id: galleriesId })); - }, [galleriesId]); + dispatch(fetch({ id: periodsId })); + }, [periodsId]); useEffect(() => { - if (typeof galleries === 'object') { - setInitialValues(galleries); + if (typeof periods === 'object') { + setInitialValues(periods); } - }, [galleries]); + }, [periods]); useEffect(() => { - if (typeof galleries === 'object') { + if (typeof periods === 'object') { const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = galleries[el]), - ); + Object.keys(initVals).forEach((el) => (newInitialVal[el] = periods[el])); setInitialValues(newInitialVal); } - }, [galleries]); + }, [periods]); const handleSubmit = async (data) => { - await dispatch(update({ id: galleriesId, data })); - await router.push('/galleries/galleries-list'); + await dispatch(update({ id: periodsId, data })); + await router.push('/periods/periods-list'); }; return ( <> - {getPageTitle('Edit galleries')} + {getPageTitle('Edit periods')} {''} @@ -110,20 +110,42 @@ const EditGalleries = () => { > - - + + + setInitialValues({ ...initialValues, period_from: date }) + } + /> + + + + + setInitialValues({ ...initialValues, period_to: date }) + } + /> @@ -135,7 +157,7 @@ const EditGalleries = () => { color='danger' outline label='Cancel' - onClick={() => router.push('/galleries/galleries-list')} + onClick={() => router.push('/periods/periods-list')} /> @@ -146,12 +168,12 @@ const EditGalleries = () => { ); }; -EditGalleries.getLayout = function getLayout(page: ReactElement) { +EditPeriods.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ); }; -export default EditGalleries; +export default EditPeriods; diff --git a/frontend/src/pages/galleries/galleries-edit.tsx b/frontend/src/pages/periods/periods-edit.tsx similarity index 63% rename from frontend/src/pages/galleries/galleries-edit.tsx rename to frontend/src/pages/periods/periods-edit.tsx index ca98918..b03a06c 100644 --- a/frontend/src/pages/galleries/galleries-edit.tsx +++ b/frontend/src/pages/periods/periods-edit.tsx @@ -25,14 +25,14 @@ import { SelectFieldMany } from '../../components/SelectFieldMany'; import { SwitchField } from '../../components/SwitchField'; import { RichTextField } from '../../components/RichTextField'; -import { update, fetch } from '../../stores/galleries/galleriesSlice'; +import { update, fetch } from '../../stores/periods/periodsSlice'; 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 EditGalleriesPage = () => { +const EditPeriodsPage = () => { const router = useRouter(); const dispatch = useAppDispatch(); const initVals = { @@ -40,11 +40,13 @@ const EditGalleriesPage = () => { archival_items: [], - background_image: [], + period_from: new Date(), + + period_to: new Date(), }; const [initialValues, setInitialValues] = useState(initVals); - const { galleries } = useAppSelector((state) => state.galleries); + const { periods } = useAppSelector((state) => state.periods); const { id } = router.query; @@ -53,35 +55,33 @@ const EditGalleriesPage = () => { }, [id]); useEffect(() => { - if (typeof galleries === 'object') { - setInitialValues(galleries); + if (typeof periods === 'object') { + setInitialValues(periods); } - }, [galleries]); + }, [periods]); useEffect(() => { - if (typeof galleries === 'object') { + if (typeof periods === 'object') { const newInitialVal = { ...initVals }; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = galleries[el]), - ); + Object.keys(initVals).forEach((el) => (newInitialVal[el] = periods[el])); setInitialValues(newInitialVal); } - }, [galleries]); + }, [periods]); const handleSubmit = async (data) => { await dispatch(update({ id: id, data })); - await router.push('/galleries/galleries-list'); + await router.push('/periods/periods-list'); }; return ( <> - {getPageTitle('Edit galleries')} + {getPageTitle('Edit periods')} {''} @@ -108,20 +108,42 @@ const EditGalleriesPage = () => { > - - + + + setInitialValues({ ...initialValues, period_from: date }) + } + /> + + + + + setInitialValues({ ...initialValues, period_to: date }) + } + /> @@ -133,7 +155,7 @@ const EditGalleriesPage = () => { color='danger' outline label='Cancel' - onClick={() => router.push('/galleries/galleries-list')} + onClick={() => router.push('/periods/periods-list')} /> @@ -144,12 +166,12 @@ const EditGalleriesPage = () => { ); }; -EditGalleriesPage.getLayout = function getLayout(page: ReactElement) { +EditPeriodsPage.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ); }; -export default EditGalleriesPage; +export default EditPeriodsPage; diff --git a/frontend/src/pages/galleries/galleries-list.tsx b/frontend/src/pages/periods/periods-list.tsx similarity index 83% rename from frontend/src/pages/galleries/galleries-list.tsx rename to frontend/src/pages/periods/periods-list.tsx index 29a2812..ad69e38 100644 --- a/frontend/src/pages/galleries/galleries-list.tsx +++ b/frontend/src/pages/periods/periods-list.tsx @@ -7,18 +7,18 @@ import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableGalleries from '../../components/Galleries/TableGalleries'; +import TablePeriods from '../../components/Periods/TablePeriods'; 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/galleries/galleriesSlice'; +import { setRefetch, uploadCsv } from '../../stores/periods/periodsSlice'; import { hasPermission } from '../../helpers/userPermissions'; -const GalleriesTablesPage = () => { +const PeriodsTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -31,11 +31,14 @@ const GalleriesTablesPage = () => { const [filters] = useState([ { label: 'Name', title: 'name' }, + { label: 'Period From', title: 'period_from', date: 'true' }, + { label: 'Period To', title: 'period_to', date: 'true' }, + { label: 'Archival Items', title: 'archival_items' }, ]); const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_GALLERIES'); + currentUser && hasPermission(currentUser, 'CREATE_PERIODS'); const addFilter = () => { const newItem = { @@ -51,9 +54,9 @@ const GalleriesTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getGalleriesCSV = async () => { + const getPeriodsCSV = async () => { const response = await axios({ - url: '/galleries?filetype=csv', + url: '/periods?filetype=csv', method: 'GET', responseType: 'blob', }); @@ -61,7 +64,7 @@ const GalleriesTablesPage = () => { const blob = new Blob([response.data], { type: type }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); - link.download = 'galleriesCSV.csv'; + link.download = 'periodsCSV.csv'; link.click(); }; @@ -81,12 +84,12 @@ const GalleriesTablesPage = () => { return ( <> - {getPageTitle('Galleries')} + {getPageTitle('Periods')} {''} @@ -95,7 +98,7 @@ const GalleriesTablesPage = () => { {hasCreatePermission && ( @@ -111,7 +114,7 @@ const GalleriesTablesPage = () => { className={'mr-3'} color='info' label='Download CSV' - onClick={getGalleriesCSV} + onClick={getPeriodsCSV} /> {hasCreatePermission && ( @@ -128,7 +131,7 @@ const GalleriesTablesPage = () => { - { ); }; -GalleriesTablesPage.getLayout = function getLayout(page: ReactElement) { +PeriodsTablesPage.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ); }; -export default GalleriesTablesPage; +export default PeriodsTablesPage; diff --git a/frontend/src/pages/galleries/galleries-new.tsx b/frontend/src/pages/periods/periods-new.tsx similarity index 78% rename from frontend/src/pages/galleries/galleries-new.tsx rename to frontend/src/pages/periods/periods-new.tsx index 17f4c48..445eccb 100644 --- a/frontend/src/pages/galleries/galleries-new.tsx +++ b/frontend/src/pages/periods/periods-new.tsx @@ -27,7 +27,7 @@ import { SelectField } from '../../components/SelectField'; import { SelectFieldMany } from '../../components/SelectFieldMany'; import { RichTextField } from '../../components/RichTextField'; -import { create } from '../../stores/galleries/galleriesSlice'; +import { create } from '../../stores/periods/periodsSlice'; import { useAppDispatch } from '../../stores/hooks'; import { useRouter } from 'next/router'; import moment from 'moment'; @@ -37,16 +37,18 @@ const initialValues = { archival_items: [], - background_image: [], + period_from: '', + + period_to: '', }; -const GalleriesNew = () => { +const PeriodsNew = () => { const router = useRouter(); const dispatch = useAppDispatch(); const handleSubmit = async (data) => { await dispatch(create(data)); - await router.push('/galleries/galleries-list'); + await router.push('/periods/periods-list'); }; return ( <> @@ -81,20 +83,20 @@ const GalleriesNew = () => { > - + + type='datetime-local' + name='period_from' + placeholder='Period From' + /> + + + + @@ -106,7 +108,7 @@ const GalleriesNew = () => { color='danger' outline label='Cancel' - onClick={() => router.push('/galleries/galleries-list')} + onClick={() => router.push('/periods/periods-list')} /> @@ -117,12 +119,12 @@ const GalleriesNew = () => { ); }; -GalleriesNew.getLayout = function getLayout(page: ReactElement) { +PeriodsNew.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ); }; -export default GalleriesNew; +export default PeriodsNew; diff --git a/frontend/src/pages/galleries/galleries-table.tsx b/frontend/src/pages/periods/periods-table.tsx similarity index 83% rename from frontend/src/pages/galleries/galleries-table.tsx rename to frontend/src/pages/periods/periods-table.tsx index c1639cc..c7d95e8 100644 --- a/frontend/src/pages/galleries/galleries-table.tsx +++ b/frontend/src/pages/periods/periods-table.tsx @@ -7,18 +7,18 @@ import LayoutAuthenticated from '../../layouts/Authenticated'; import SectionMain from '../../components/SectionMain'; import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton'; import { getPageTitle } from '../../config'; -import TableGalleries from '../../components/Galleries/TableGalleries'; +import TablePeriods from '../../components/Periods/TablePeriods'; 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/galleries/galleriesSlice'; +import { setRefetch, uploadCsv } from '../../stores/periods/periodsSlice'; import { hasPermission } from '../../helpers/userPermissions'; -const GalleriesTablesPage = () => { +const PeriodsTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); @@ -31,11 +31,14 @@ const GalleriesTablesPage = () => { const [filters] = useState([ { label: 'Name', title: 'name' }, + { label: 'Period From', title: 'period_from', date: 'true' }, + { label: 'Period To', title: 'period_to', date: 'true' }, + { label: 'Archival Items', title: 'archival_items' }, ]); const hasCreatePermission = - currentUser && hasPermission(currentUser, 'CREATE_GALLERIES'); + currentUser && hasPermission(currentUser, 'CREATE_PERIODS'); const addFilter = () => { const newItem = { @@ -51,9 +54,9 @@ const GalleriesTablesPage = () => { setFilterItems([...filterItems, newItem]); }; - const getGalleriesCSV = async () => { + const getPeriodsCSV = async () => { const response = await axios({ - url: '/galleries?filetype=csv', + url: '/periods?filetype=csv', method: 'GET', responseType: 'blob', }); @@ -61,7 +64,7 @@ const GalleriesTablesPage = () => { const blob = new Blob([response.data], { type: type }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); - link.download = 'galleriesCSV.csv'; + link.download = 'periodsCSV.csv'; link.click(); }; @@ -81,12 +84,12 @@ const GalleriesTablesPage = () => { return ( <> - {getPageTitle('Galleries')} + {getPageTitle('Periods')} {''} @@ -95,7 +98,7 @@ const GalleriesTablesPage = () => { {hasCreatePermission && ( @@ -111,7 +114,7 @@ const GalleriesTablesPage = () => { className={'mr-3'} color='info' label='Download CSV' - onClick={getGalleriesCSV} + onClick={getPeriodsCSV} /> {hasCreatePermission && ( @@ -127,7 +130,7 @@ const GalleriesTablesPage = () => {
    - { ); }; -GalleriesTablesPage.getLayout = function getLayout(page: ReactElement) { +PeriodsTablesPage.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ); }; -export default GalleriesTablesPage; +export default PeriodsTablesPage; diff --git a/frontend/src/pages/galleries/galleries-view.tsx b/frontend/src/pages/periods/periods-view.tsx similarity index 65% rename from frontend/src/pages/galleries/galleries-view.tsx rename to frontend/src/pages/periods/periods-view.tsx index 3d39910..bab4f6a 100644 --- a/frontend/src/pages/galleries/galleries-view.tsx +++ b/frontend/src/pages/periods/periods-view.tsx @@ -5,7 +5,7 @@ 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/galleries/galleriesSlice'; +import { fetch } from '../../stores/periods/periodsSlice'; import { saveFile } from '../../helpers/fileSaver'; import dataFormatter from '../../helpers/dataFormatter'; import ImageField from '../../components/ImageField'; @@ -20,10 +20,10 @@ import { mdiChartTimelineVariant } from '@mdi/js'; import { SwitchField } from '../../components/SwitchField'; import FormField from '../../components/FormField'; -const GalleriesView = () => { +const PeriodsView = () => { const router = useRouter(); const dispatch = useAppDispatch(); - const { galleries } = useAppSelector((state) => state.galleries); + const { periods } = useAppSelector((state) => state.periods); const { id } = router.query; @@ -39,24 +39,24 @@ const GalleriesView = () => { return ( <> - {getPageTitle('View galleries')} + {getPageTitle('View periods')}

    Name

    -

    {galleries?.name}

    +

    {periods?.name}

    <> @@ -74,16 +74,12 @@ const GalleriesView = () => { Description Is Published - - Period From - - Period To - {galleries.archival_items && - Array.isArray(galleries.archival_items) && - galleries.archival_items.map((item: any) => ( + {periods.archival_items && + Array.isArray(periods.archival_items) && + periods.archival_items.map((item: any) => ( @@ -99,42 +95,61 @@ const GalleriesView = () => { {dataFormatter.booleanFormatter(item.isPublished)} - - - {dataFormatter.dateFormatter(item.period_from)} - - - {item.period_to} ))}
    - {!galleries?.archival_items?.length && ( + {!periods?.archival_items?.length && (
    No data
    )} -
    -

    Background Image

    - {galleries?.background_image?.length ? ( - + {periods.period_from ? ( + ) : ( -

    No Background Image

    +

    No Period From

    )} -
    + + + + {periods.period_to ? ( + + ) : ( +

    No Period To

    + )} +
    router.push('/galleries/galleries-list')} + onClick={() => router.push('/periods/periods-list')} /> @@ -142,12 +157,12 @@ const GalleriesView = () => { ); }; -GalleriesView.getLayout = function getLayout(page: ReactElement) { +PeriodsView.getLayout = function getLayout(page: ReactElement) { return ( - + {page} ); }; -export default GalleriesView; +export default PeriodsView; diff --git a/frontend/src/stores/galleries/galleriesSlice.ts b/frontend/src/stores/periods/periodsSlice.ts similarity index 79% rename from frontend/src/stores/galleries/galleriesSlice.ts rename to frontend/src/stores/periods/periodsSlice.ts index 87a5ba7..8e5f300 100644 --- a/frontend/src/stores/galleries/galleriesSlice.ts +++ b/frontend/src/stores/periods/periodsSlice.ts @@ -7,7 +7,7 @@ import { } from '../../helpers/notifyStateHandler'; interface MainState { - galleries: any; + periods: any; loading: boolean; count: number; refetch: boolean; @@ -20,7 +20,7 @@ interface MainState { } const initialState: MainState = { - galleries: [], + periods: [], loading: false, count: 0, refetch: false, @@ -32,19 +32,19 @@ const initialState: MainState = { }, }; -export const fetch = createAsyncThunk('galleries/fetch', async (data: any) => { +export const fetch = createAsyncThunk('periods/fetch', async (data: any) => { const { id, query } = data; - const result = await axios.get(`galleries${query || (id ? `/${id}` : '')}`); + const result = await axios.get(`periods${query || (id ? `/${id}` : '')}`); return id ? result.data : { rows: result.data.rows, count: result.data.count }; }); export const deleteItemsByIds = createAsyncThunk( - 'galleries/deleteByIds', + 'periods/deleteByIds', async (data: any, { rejectWithValue }) => { try { - await axios.post('galleries/deleteByIds', { data }); + await axios.post('periods/deleteByIds', { data }); } catch (error) { if (!error.response) { throw error; @@ -56,10 +56,10 @@ export const deleteItemsByIds = createAsyncThunk( ); export const deleteItem = createAsyncThunk( - 'galleries/deleteGalleries', + 'periods/deletePeriods', async (id: string, { rejectWithValue }) => { try { - await axios.delete(`galleries/${id}`); + await axios.delete(`periods/${id}`); } catch (error) { if (!error.response) { throw error; @@ -71,10 +71,10 @@ export const deleteItem = createAsyncThunk( ); export const create = createAsyncThunk( - 'galleries/createGalleries', + 'periods/createPeriods', async (data: any, { rejectWithValue }) => { try { - const result = await axios.post('galleries', { data }); + const result = await axios.post('periods', { data }); return result.data; } catch (error) { if (!error.response) { @@ -87,14 +87,14 @@ export const create = createAsyncThunk( ); export const uploadCsv = createAsyncThunk( - 'galleries/uploadCsv', + 'periods/uploadCsv', async (file: File, { rejectWithValue }) => { try { const data = new FormData(); data.append('file', file); data.append('filename', file.name); - const result = await axios.post('galleries/bulk-import', data, { + const result = await axios.post('periods/bulk-import', data, { headers: { 'Content-Type': 'multipart/form-data', }, @@ -112,10 +112,10 @@ export const uploadCsv = createAsyncThunk( ); export const update = createAsyncThunk( - 'galleries/updateGalleries', + 'periods/updatePeriods', async (payload: any, { rejectWithValue }) => { try { - const result = await axios.put(`galleries/${payload.id}`, { + const result = await axios.put(`periods/${payload.id}`, { id: payload.id, data: payload.data, }); @@ -130,8 +130,8 @@ export const update = createAsyncThunk( }, ); -export const galleriesSlice = createSlice({ - name: 'galleries', +export const periodsSlice = createSlice({ + name: 'periods', initialState, reducers: { setRefetch: (state, action: PayloadAction) => { @@ -150,10 +150,10 @@ export const galleriesSlice = createSlice({ builder.addCase(fetch.fulfilled, (state, action) => { if (action.payload.rows && action.payload.count >= 0) { - state.galleries = action.payload.rows; + state.periods = action.payload.rows; state.count = action.payload.count; } else { - state.galleries = action.payload; + state.periods = action.payload; } state.loading = false; }); @@ -165,7 +165,7 @@ export const galleriesSlice = createSlice({ builder.addCase(deleteItemsByIds.fulfilled, (state) => { state.loading = false; - fulfilledNotify(state, 'Galleries has been deleted'); + fulfilledNotify(state, 'Periods has been deleted'); }); builder.addCase(deleteItemsByIds.rejected, (state, action) => { @@ -180,7 +180,7 @@ export const galleriesSlice = createSlice({ builder.addCase(deleteItem.fulfilled, (state) => { state.loading = false; - fulfilledNotify(state, `${'Galleries'.slice(0, -1)} has been deleted`); + fulfilledNotify(state, `${'Periods'.slice(0, -1)} has been deleted`); }); builder.addCase(deleteItem.rejected, (state, action) => { @@ -199,7 +199,7 @@ export const galleriesSlice = createSlice({ builder.addCase(create.fulfilled, (state) => { state.loading = false; - fulfilledNotify(state, `${'Galleries'.slice(0, -1)} has been created`); + fulfilledNotify(state, `${'Periods'.slice(0, -1)} has been created`); }); builder.addCase(update.pending, (state) => { @@ -208,7 +208,7 @@ export const galleriesSlice = createSlice({ }); builder.addCase(update.fulfilled, (state) => { state.loading = false; - fulfilledNotify(state, `${'Galleries'.slice(0, -1)} has been updated`); + fulfilledNotify(state, `${'Periods'.slice(0, -1)} has been updated`); }); builder.addCase(update.rejected, (state, action) => { state.loading = false; @@ -221,7 +221,7 @@ export const galleriesSlice = createSlice({ }); builder.addCase(uploadCsv.fulfilled, (state) => { state.loading = false; - fulfilledNotify(state, 'Galleries has been uploaded'); + fulfilledNotify(state, 'Periods has been uploaded'); }); builder.addCase(uploadCsv.rejected, (state, action) => { state.loading = false; @@ -231,6 +231,6 @@ export const galleriesSlice = createSlice({ }); // Action creators are generated for each case reducer function -export const { setRefetch } = galleriesSlice.actions; +export const { setRefetch } = periodsSlice.actions; -export default galleriesSlice.reducer; +export default periodsSlice.reducer; diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts index b4586af..8eb009a 100644 --- a/frontend/src/stores/store.ts +++ b/frontend/src/stores/store.ts @@ -6,7 +6,7 @@ import openAiSlice from './openAiSlice'; import usersSlice from './users/usersSlice'; import archival_itemsSlice from './archival_items/archival_itemsSlice'; -import galleriesSlice from './galleries/galleriesSlice'; +import periodsSlice from './periods/periodsSlice'; import tagsSlice from './tags/tagsSlice'; import rolesSlice from './roles/rolesSlice'; import permissionsSlice from './permissions/permissionsSlice'; @@ -20,7 +20,7 @@ export const store = configureStore({ users: usersSlice, archival_items: archival_itemsSlice, - galleries: galleriesSlice, + periods: periodsSlice, tags: tagsSlice, roles: rolesSlice, permissions: permissionsSlice,