diff --git a/back/methods/starred-module/getStarredModules.js b/back/methods/starred-module/getStarredModules.js index 88c0fa8a9..7b0f0e945 100644 --- a/back/methods/starred-module/getStarredModules.js +++ b/back/methods/starred-module/getStarredModules.js @@ -12,17 +12,23 @@ module.exports = function(Self) { } }); - Self.getStarredModules = async ctx => { + Self.getStarredModules = async(ctx, options) => { + const models = Self.app.models; const userId = ctx.req.accessToken.userId; + + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + const filter = { where: { workerFk: userId }, - fields: ['moduleFk'] + fields: ['id', 'workerFk', 'moduleFk', 'position'], + order: 'position ASC' }; - const starredModules = await Self.app.models.StarredModule.find(filter); - - return starredModules; + return models.StarredModule.find(filter, myOptions); }; }; diff --git a/back/methods/starred-module/setPosition.js b/back/methods/starred-module/setPosition.js new file mode 100644 index 000000000..c72de1083 --- /dev/null +++ b/back/methods/starred-module/setPosition.js @@ -0,0 +1,98 @@ +module.exports = function(Self) { + Self.remoteMethodCtx('setPosition', { + description: 'sets the position of a given module', + accessType: 'WRITE', + returns: { + type: 'object', + root: true + }, + accepts: [ + { + arg: 'moduleName', + type: 'string', + required: true, + description: 'The module name' + }, + { + arg: 'direction', + type: 'string', + required: true, + description: 'Whether to move left or right the module position' + } + ], + http: { + path: `/setPosition`, + verb: 'post' + } + }); + + Self.setPosition = async(ctx, moduleName, direction, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + + let tx; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const filter = { + where: { + workerFk: userId, + moduleFk: moduleName + }, + order: 'position DESC' + }; + + const [movingModule] = await models.StarredModule.find(filter, myOptions); + + let operator; + let order; + + switch (direction) { + case 'left': + operator = {lt: movingModule.position}; + order = 'position DESC'; + break; + case 'right': + operator = {gt: movingModule.position}; + order = 'position ASC'; + break; + default: + return; + } + + const pushedModule = await models.StarredModule.findOne({ + where: { + position: operator, + workerFk: userId + }, + order: order + }, myOptions); + + if (!pushedModule) return; + + const movingPosition = pushedModule.position; + const pushingPosition = movingModule.position; + + await movingModule.updateAttribute('position', movingPosition, myOptions); + await pushedModule.updateAttribute('position', pushingPosition, myOptions); + + if (tx) await tx.commit(); + + return { + movingModule: movingModule, + pushedModule: pushedModule + }; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/back/methods/starred-module/specs/getStarredModules.spec.js b/back/methods/starred-module/specs/getStarredModules.spec.js index c39ed57e9..b5d2f25c8 100644 --- a/back/methods/starred-module/specs/getStarredModules.spec.js +++ b/back/methods/starred-module/specs/getStarredModules.spec.js @@ -19,7 +19,7 @@ describe('getStarredModules()', () => { }); it(`should return the starred modules for a given user`, async() => { - const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'Clients'}); + const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'Clients', position: 1}); const starredModules = await app.models.StarredModule.getStarredModules(ctx); expect(starredModules.length).toEqual(1); diff --git a/back/methods/starred-module/specs/setPosition.spec.js b/back/methods/starred-module/specs/setPosition.spec.js new file mode 100644 index 000000000..6ac9cab16 --- /dev/null +++ b/back/methods/starred-module/specs/setPosition.spec.js @@ -0,0 +1,223 @@ +const app = require('vn-loopback/server/server'); +const LoopBackContext = require('loopback-context'); + +describe('setPosition()', () => { + const activeCtx = { + accessToken: {userId: 9}, + http: { + req: { + headers: {origin: 'http://localhost'} + } + } + }; + const ctx = { + req: activeCtx + }; + + beforeEach(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should increase the orders module position by replacing it with clients and vice versa', async() => { + const tx = await app.models.StarredModule.beginTransaction({}); + + const filter = { + where: { + workerFk: ctx.req.accessToken.userId, + moduleFk: 'Orders' + } + }; + + try { + const options = {transaction: tx}; + await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options); + + let orders = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Clients'; + let clients = await app.models.StarredModule.findOne(filter, options); + + expect(orders.position).toEqual(1); + expect(clients.position).toEqual(2); + + await app.models.StarredModule.setPosition(ctx, 'Clients', 'left', options); + + filter.where.moduleFk = 'Clients'; + clients = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Orders'; + orders = await app.models.StarredModule.findOne(filter, options); + + expect(clients.position).toEqual(1); + expect(orders.position).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should decrease the orders module position by replacing it with clients and vice versa', async() => { + const tx = await app.models.StarredModule.beginTransaction({}); + + const filter = { + where: { + workerFk: ctx.req.accessToken.userId, + moduleFk: 'Orders' + } + }; + + try { + const options = {transaction: tx}; + await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options); + + let orders = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Clients'; + let clients = await app.models.StarredModule.findOne(filter, options); + + expect(orders.position).toEqual(1); + expect(clients.position).toEqual(2); + + await app.models.StarredModule.setPosition(ctx, 'Orders', 'right', options); + + filter.where.moduleFk = 'Orders'; + orders = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Clients'; + clients = await app.models.StarredModule.findOne(filter, options); + + expect(orders.position).toEqual(2); + expect(clients.position).toEqual(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should switch two modules after adding and deleting several modules', async() => { + const tx = await app.models.StarredModule.beginTransaction({}); + + const filter = { + where: { + workerFk: ctx.req.accessToken.userId, + moduleFk: 'Items' + } + }; + + try { + const options = {transaction: tx}; + + await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options); + + const items = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Claims'; + const claims = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Clients'; + let clients = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Orders'; + let orders = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Zones'; + const zones = await app.models.StarredModule.findOne(filter, options); + + expect(items.position).toEqual(1); + expect(claims.position).toEqual(2); + expect(clients.position).toEqual(3); + expect(orders.position).toEqual(4); + expect(zones.position).toEqual(5); + + await app.models.StarredModule.setPosition(ctx, 'Clients', 'right', options); + + filter.where.moduleFk = 'Orders'; + orders = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Clients'; + clients = await app.models.StarredModule.findOne(filter, options); + + expect(orders.position).toEqual(3); + expect(clients.position).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should switch two modules after adding and deleting a module between them', async() => { + const tx = await app.models.StarredModule.beginTransaction({}); + + const filter = { + where: { + workerFk: ctx.req.accessToken.userId, + moduleFk: 'Items' + } + }; + + try { + const options = {transaction: tx}; + + await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options); + await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options); + + const items = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Clients'; + let clients = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Claims'; + const claims = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Orders'; + let orders = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Zones'; + const zones = await app.models.StarredModule.findOne(filter, options); + + expect(items.position).toEqual(1); + expect(clients.position).toEqual(2); + expect(claims.position).toEqual(3); + expect(orders.position).toEqual(4); + expect(zones.position).toEqual(5); + + await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options); + await app.models.StarredModule.setPosition(ctx, 'Clients', 'right', options); + + filter.where.moduleFk = 'Clients'; + clients = await app.models.StarredModule.findOne(filter, options); + + filter.where.moduleFk = 'Orders'; + orders = await app.models.StarredModule.findOne(filter, options); + + expect(orders.position).toEqual(2); + expect(clients.position).toEqual(4); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/back/methods/starred-module/specs/toggleStarredModule.spec.js b/back/methods/starred-module/specs/toggleStarredModule.spec.js index a765a29a0..1aed4f54a 100644 --- a/back/methods/starred-module/specs/toggleStarredModule.spec.js +++ b/back/methods/starred-module/specs/toggleStarredModule.spec.js @@ -27,6 +27,7 @@ describe('toggleStarredModule()', () => { expect(starredModules.length).toEqual(1); expect(starredModule.moduleFk).toEqual('Orders'); expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId); + expect(starredModule.position).toEqual(starredModules.length); await app.models.StarredModule.toggleStarredModule(ctx, 'Orders'); starredModules = await app.models.StarredModule.getStarredModules(ctx); diff --git a/back/methods/starred-module/toggleStarredModule.js b/back/methods/starred-module/toggleStarredModule.js index 38d82eba4..16e14740b 100644 --- a/back/methods/starred-module/toggleStarredModule.js +++ b/back/methods/starred-module/toggleStarredModule.js @@ -18,24 +18,61 @@ module.exports = function(Self) { } }); - Self.toggleStarredModule = async(ctx, moduleName) => { + Self.toggleStarredModule = async(ctx, moduleName, options) => { + const models = Self.app.models; const userId = ctx.req.accessToken.userId; - const filter = { - where: { - workerFk: userId, - moduleFk: moduleName + + let tx; + let myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const filter = { + where: { + workerFk: userId, + moduleFk: moduleName + } + }; + + const [starredModule] = await models.StarredModule.find(filter, myOptions); + + delete filter.moduleName; + const allStarredModules = await models.StarredModule.getStarredModules(ctx, myOptions); + + let addedModule; + + if (starredModule) + await starredModule.destroy(myOptions); + else { + let highestPosition; + if (allStarredModules.length) { + allStarredModules.sort((a, b) => { + return a.position - b.position; + }); + highestPosition = allStarredModules[allStarredModules.length - 1].position + 1; + } else + highestPosition = 1; + + addedModule = await models.StarredModule.create({ + workerFk: userId, + moduleFk: moduleName, + position: highestPosition + }, myOptions); } - }; - const [starredModule] = await Self.app.models.StarredModule.find(filter); + if (tx) await tx.commit(); - if (starredModule) - await starredModule.destroy(); - else { - return Self.app.models.StarredModule.create({ - workerFk: userId, - moduleFk: moduleName - }); + return addedModule; + } catch (e) { + if (tx) await tx.rollback(); + throw e; } }; }; diff --git a/back/models/starred-module.js b/back/models/starred-module.js index f153003ca..5753fdc85 100644 --- a/back/models/starred-module.js +++ b/back/models/starred-module.js @@ -1,4 +1,5 @@ module.exports = Self => { require('../methods/starred-module/getStarredModules')(Self); require('../methods/starred-module/toggleStarredModule')(Self); + require('../methods/starred-module/setPosition')(Self); }; diff --git a/back/models/starred-module.json b/back/models/starred-module.json index e383fa17a..ec4c3ce03 100644 --- a/back/models/starred-module.json +++ b/back/models/starred-module.json @@ -18,6 +18,10 @@ "moduleFk": { "type": "string", "required": true + }, + "position": { + "type": "number", + "required": true } }, "relations": { diff --git a/db/changes/10320-monitors/02-starredModule.sql b/db/changes/10320-monitors/02-starredModule.sql new file mode 100644 index 000000000..595d9acf0 --- /dev/null +++ b/db/changes/10320-monitors/02-starredModule.sql @@ -0,0 +1,2 @@ +ALTER TABLE `vn`.`starredModule` + ADD `position` INT NOT NULL AFTER `moduleFk`; \ No newline at end of file diff --git a/front/salix/components/home/home.html b/front/salix/components/home/home.html index 2904d7d70..7b716fc4f 100644 --- a/front/salix/components/home/home.html +++ b/front/salix/components/home/home.html @@ -9,18 +9,33 @@
- -
- -
+ + + + + + + +
@@ -41,17 +56,19 @@
-
- -
+ +
+ +
+
diff --git a/front/salix/components/home/home.js b/front/salix/components/home/home.js index 90eed2b30..74b43a12f 100644 --- a/front/salix/components/home/home.js +++ b/front/salix/components/home/home.js @@ -35,6 +35,7 @@ export default class Controller extends Component { for (let starredModule of res.data) { const module = this.modules.find(mod => mod.name === starredModule.moduleFk); module.starred = true; + module.position = starredModule.position; } this.countModules(); }); @@ -48,9 +49,10 @@ export default class Controller extends Component { const params = {moduleName: module.name}; const query = `starredModules/toggleStarredModule`; this.$http.post(query, params).then(res => { - if (res.data) + if (res.data) { module.starred = true; - else + module.position = res.data.position; + } else module.starred = false; this.vnApp.showSuccess(this.$t('Data saved!')); @@ -74,6 +76,26 @@ export default class Controller extends Component { return this.$sce.trustAsHtml(getName(mod)); } + + moveModule(module, event, direction) { + if (event.defaultPrevented) return; + event.preventDefault(); + event.stopPropagation(); + + const params = {moduleName: module.name, direction: direction}; + const query = `starredModules/setPosition`; + this.$http.post(query, params).then(res => { + if (res.data) { + module.position = res.data.movingModule.position; + this.modules.forEach(mod => { + if (mod.name == res.data.pushedModule.moduleFk) + mod.position = res.data.pushedModule.position; + }); + this.vnApp.showSuccess(this.$t('Data saved!')); + this.countModules(); + } + }); + } } Controller.$inject = ['$element', '$scope', 'vnModules', '$sce']; diff --git a/front/salix/components/home/home.spec.js b/front/salix/components/home/home.spec.js index 09cb7e39b..4a8a58a55 100644 --- a/front/salix/components/home/home.spec.js +++ b/front/salix/components/home/home.spec.js @@ -59,7 +59,7 @@ describe('Salix Component vnHome', () => { expect(controller._modules[0].starred).toBe(true); }); - it(`should set the received module as regular if it was starred`, () => { + it('should set the received module as regular if it was starred', () => { const event = new Event('target'); controller._modules = [{module: 'client', name: 'Clients', starred: true}]; @@ -72,4 +72,34 @@ describe('Salix Component vnHome', () => { expect(controller._modules[0].starred).toBe(false); }); }); + + describe('moveModule()', () => { + it('should perform a query to setPosition and the apply the position to the moved and pushed modules', () => { + const starredModules = [ + {id: 1, moduleFk: 'Clients', workerFk: 9}, + {id: 2, moduleFk: 'Orders', workerFk: 9} + ]; + + const movedModules = { + movingModule: {position: 2, moduleFk: 'Clients'}, + pushedModule: {position: 1, moduleFk: 'Orders'} + }; + const event = new Event('target'); + controller._modules = [ + {module: 'client', name: 'Clients', position: 1}, + {module: 'orders', name: 'Orders', position: 2} + ]; + + $httpBackend.whenRoute('GET', 'starredModules/getStarredModules').respond(starredModules); + $httpBackend.expectPOST('starredModules/setPosition').respond(movedModules); + + expect(controller._modules[0].position).toEqual(1); + expect(controller._modules[1].position).toEqual(2); + controller.moveModule(controller._modules[0], event, 'right'); + $httpBackend.flush(); + + expect(controller._modules[0].position).toEqual(2); + expect(controller._modules[1].position).toEqual(1); + }); + }); }); diff --git a/front/salix/components/home/locale/es.yml b/front/salix/components/home/locale/es.yml index aab63f17e..633912345 100644 --- a/front/salix/components/home/locale/es.yml +++ b/front/salix/components/home/locale/es.yml @@ -1,4 +1,6 @@ Favorites: Favoritos You can set modules as favorites by clicking their icon: Puedes establecer módulos como favoritos haciendo clic en el icono -Add to favorites: Añadir a favoritos. -Remove from favorites: Quitar de favoritos. \ No newline at end of file +Add to favorites: Añadir a favoritos +Remove from favorites: Quitar de favoritos +Move left: Mover a la izquierda +Move right: Mover a la derecha \ No newline at end of file diff --git a/front/salix/components/home/style.scss b/front/salix/components/home/style.scss index c8ef6457f..bfe97dd7d 100644 --- a/front/salix/components/home/style.scss +++ b/front/salix/components/home/style.scss @@ -52,6 +52,11 @@ vn-home { max-width: 704px; margin: 0 auto; + & > a:first-child span vn-icon[icon="arrow_left"], + & > a:last-child span vn-icon[icon="arrow_right"] { + visibility: hidden; + } + & > a { @extend %clickable-light; display: flex; @@ -66,19 +71,18 @@ vn-home { background-color: $color-button; color: $color-font-dark; - & .pin { + & .small-icon { + display: inline-flex; opacity: 0; - flex-direction: row; - justify-content: left; - height: 20px; - width: 20px; vn-icon { margin: auto; font-size: 1.5rem; } } - &:hover .pin { + &:hover .small-icon { + flex-direction: row; opacity: 1; + } & > div {