From 03ab88bcd9c07dc0eff8c463434fff2718e267e6 Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Tue, 5 Nov 2019 08:59:48 +0100 Subject: [PATCH 01/41] #1852 worker.time-control --- front/core/components/chip/style.scss | 8 +- .../{addTime.js => addTimeEntry.js} | 6 +- .../worker-time-control/deleteTimeEntry.js | 35 ++++ .../back/methods/worker/getWorkedHours.js | 67 ++++++ .../worker/back/methods/worker/timeControl.js | 50 +++++ .../worker/back/models/worker-time-control.js | 3 +- .../back/models/worker-time-control.json | 8 + modules/worker/back/models/worker.js | 1 + modules/worker/front/time-control/index.html | 65 ++++-- modules/worker/front/time-control/index.js | 194 +++++++++++++----- .../worker/front/time-control/locale/es.yml | 5 +- modules/worker/front/time-control/style.scss | 2 +- 12 files changed, 372 insertions(+), 72 deletions(-) rename modules/worker/back/methods/worker-time-control/{addTime.js => addTimeEntry.js} (91%) create mode 100644 modules/worker/back/methods/worker-time-control/deleteTimeEntry.js create mode 100644 modules/worker/back/methods/worker/getWorkedHours.js create mode 100644 modules/worker/back/methods/worker/timeControl.js diff --git a/front/core/components/chip/style.scss b/front/core/components/chip/style.scss index ee7f46848..0d19fc957 100644 --- a/front/core/components/chip/style.scss +++ b/front/core/components/chip/style.scss @@ -1,7 +1,7 @@ @import "variables"; vn-chip { - border-radius: 16px; + border-radius: 1em; background-color: $color-bg; margin: 0 0.5em 0.5em 0; color: $color-font; @@ -11,7 +11,7 @@ vn-chip { align-items: center; text-overflow: ellipsis; white-space: nowrap; - height: 28px; + height: 2em; padding: 0 .7em; overflow: hidden; @@ -47,7 +47,7 @@ vn-chip { vn-avatar { display: inline-block; - height: 28px; - width: 28px; + height: 2em; + width: 2em; border-radius: 50%; } \ No newline at end of file diff --git a/modules/worker/back/methods/worker-time-control/addTime.js b/modules/worker/back/methods/worker-time-control/addTimeEntry.js similarity index 91% rename from modules/worker/back/methods/worker-time-control/addTime.js rename to modules/worker/back/methods/worker-time-control/addTimeEntry.js index 8130a16fd..649364151 100644 --- a/modules/worker/back/methods/worker-time-control/addTime.js +++ b/modules/worker/back/methods/worker-time-control/addTimeEntry.js @@ -1,7 +1,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { - Self.remoteMethodCtx('addTime', { + Self.remoteMethodCtx('addTimeEntry', { description: 'Adds a new hour registry', accessType: 'WRITE', accepts: [{ @@ -16,12 +16,12 @@ module.exports = Self => { root: true }], http: { - path: `/addTime`, + path: `/addTimeEntry`, verb: 'POST' } }); - Self.addTime = async(ctx, data) => { + Self.addTimeEntry = async(ctx, data) => { const Worker = Self.app.models.Worker; const myUserId = ctx.req.accessToken.userId; const myWorker = await Worker.findOne({where: {userFk: myUserId}}); diff --git a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js new file mode 100644 index 000000000..717d4ce8c --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js @@ -0,0 +1,35 @@ +module.exports = Self => { + Self.remoteMethodCtx('deleteTimeEntry', { + description: 'Deletes a manual time entry for a worker if the user role is above the worker', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The time entry id', + http: {source: 'path'} + }], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/:id/deleteTimeEntry`, + verb: 'POST' + } + }); + + Self.deleteTimeEntry = async(ctx, id) => { + const workerModel = Self.app.models.Worker; + + const targetTimeEntry = await Self.findById(id); + + const hasRightsToDelete = await workerModel.isSubordinate(ctx, targetTimeEntry.userFk); + + if (!hasRightsToDelete) + throw new UserError(`You don't have enough privileges`); + + return Self.rawSql('CALL vn.workerTimeControl_remove(?, ?)', [ + targetTimeEntry.userFk, targetTimeEntry.timed]); + }; +}; diff --git a/modules/worker/back/methods/worker/getWorkedHours.js b/modules/worker/back/methods/worker/getWorkedHours.js new file mode 100644 index 000000000..e96d30f8d --- /dev/null +++ b/modules/worker/back/methods/worker/getWorkedHours.js @@ -0,0 +1,67 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; + +module.exports = Self => { + Self.remoteMethod('getWorkedHours', { + description: 'returns the total worked hours per day for a given range of dates in format YYYY-mm-dd hh:mm:ss', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'from', + type: 'Date', + required: true, + description: `The from date` + }, + { + arg: 'to', + type: 'Date', + required: true, + description: `The to date` + }], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/:id/getWorkedHours`, + verb: 'GET' + } + }); + + Self.getWorkedHours = async(id, from, to) => { + const conn = Self.dataSource.connector; + const stmts = []; + + let worker = await Self.app.models.Worker.findById(id); + let userId = worker.userFk; + + stmts.push(` + DROP TEMPORARY TABLE IF EXISTS + tmp.timeControlCalculate, + tmp.timeBusinessCalculate + `); + + stmts.push(new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [userId, from, to])); + stmts.push(new ParameterizedSQL('CALL vn.timeBusiness_calculateByUser(?, ?, ?)', [userId, from, to])); + let resultIndex = stmts.push(` + SELECT tbc.dated, tbc.timeWorkSeconds expectedHours, tcc.timeWorkSeconds workedHours + FROM tmp.timeBusinessCalculate tbc + LEFT JOIN tmp.timeControlCalculate tcc ON tcc.dated = tbc.dated + `) - 1; + stmts.push(` + DROP TEMPORARY TABLE IF EXISTS + tmp.timeControlCalculate, + tmp.timeBusinessCalculate + `); + + let sql = ParameterizedSQL.join(stmts, ';'); + let result = await conn.executeStmt(sql); + + return result[resultIndex]; + }; +}; diff --git a/modules/worker/back/methods/worker/timeControl.js b/modules/worker/back/methods/worker/timeControl.js new file mode 100644 index 000000000..bc88197fe --- /dev/null +++ b/modules/worker/back/methods/worker/timeControl.js @@ -0,0 +1,50 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; + +module.exports = Self => { + Self.remoteMethod('timeControl', { + description: 'Returns a range of worked hours for a given worker id', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'dateFrom', + type: 'datetime', + required: true, + description: 'the date from the time control begins in format YYYY-mm-dd-hh:mm:ss' + }, + { + arg: 'dateTo', + type: 'datetime', + required: true, + description: 'the date when the time control finishes in format YYYY-mm-dd-hh:mm:ss' + }], + returns: { + type: ['Object'], + root: true + }, + http: { + path: `/:id/timeControl`, + verb: 'GET' + } + }); + + Self.timeControl = async(id, from, to) => { + const conn = Self.dataSource.connector; + const stmts = []; + + stmts.push(new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [id, from, to])); + + let sql = ParameterizedSQL.join(stmts, ';'); + let result = await conn.executeStmt(sql); + + + return result[0]; + }; +}; diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index 4a065f430..f0191c36f 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -2,7 +2,8 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { require('../methods/worker-time-control/filter')(Self); - require('../methods/worker-time-control/addTime')(Self); + require('../methods/worker-time-control/addTimeEntry')(Self); + require('../methods/worker-time-control/deleteTimeEntry')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/worker/back/models/worker-time-control.json b/modules/worker/back/models/worker-time-control.json index bfd6a44aa..5212222bd 100644 --- a/modules/worker/back/models/worker-time-control.json +++ b/modules/worker/back/models/worker-time-control.json @@ -19,6 +19,9 @@ }, "order": { "type": "Number" + }, + "direction": { + "type": "string" } }, "relations": { @@ -26,6 +29,11 @@ "type": "belongsTo", "model": "Account", "foreignKey": "userFk" + }, + "worker": { + "type": "hasOne", + "model": "Worker", + "foreignKey": "userFk" }, "warehouse": { "type": "belongsTo", diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 1d2a62ce4..e49243d0f 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -3,4 +3,5 @@ module.exports = Self => { require('../methods/worker/mySubordinates')(Self); require('../methods/worker/isSubordinate')(Self); require('../methods/worker/getWorkerInfo')(Self); + require('../methods/worker/getWorkedHours')(Self); }; diff --git a/modules/worker/front/time-control/index.html b/modules/worker/front/time-control/index.html index 54c44973c..bd22a6be2 100644 --- a/modules/worker/front/time-control/index.html +++ b/modules/worker/front/time-control/index.html @@ -5,28 +5,55 @@ data="$ctrl.hours">
- + - -
{{::$ctrl.weekdayNames[$index].name}}
- {{::weekday.dated | date: 'dd'}} - - {{::weekday.dated | date: 'MMMM'}} - + +
{{::$ctrl.weekdayNames[$index].name}}
+
+ {{::weekday.dated | date: 'dd'}} + + {{::weekday.dated | date: 'MMMM'}} + +
+ + + +
+ {{::weekday.event.name}} +
+
- +
+ icon="{{ + ::hour.direction == 'in' ? 'arrow_forward' + : hour.direction == 'out' ? 'arrow_back' + : 'arrow_forward' + }}" + title="{{ + ::(hour.direction == 'in' ? 'In' + : hour.direction == 'out' ? 'Out' + : '') | translate + }}" + ng-class="::{'invisible': hour.direction == 'middle'}"> - {{hour.timed | date: 'HH:mm'}} + + {{::hour.timed | date: 'HH:mm'}} +
@@ -34,7 +61,7 @@ - {{$ctrl.getWeekdayTotalHours(weekday)}} h. + {{$ctrl.formatHours(weekday.workedHours)}} h. @@ -51,6 +78,12 @@
+
+ + +
Hours
- \ No newline at end of file + + + \ No newline at end of file diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index 622ecd16e..508669bbc 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -2,13 +2,15 @@ import ngModule from '../module'; import './style.scss'; class Controller { - constructor($scope, $http, $stateParams, $element, vnWeekDays) { + constructor($scope, $http, $stateParams, $element, vnWeekDays, vnApp, $translate) { this.$stateParams = $stateParams; this.$ = $scope; this.$http = $http; this.$element = $element; this.weekDays = []; this.weekdayNames = vnWeekDays.locales; + this.vnApp = vnApp; + this.$translate = $translate; } $postLink() { @@ -34,9 +36,20 @@ class Controller { this.started = started; let ended = new Date(started.getTime()); - ended.setDate(ended.getDate() + 7); + ended.setHours(23, 59, 59, 59); + ended.setDate(ended.getDate() + 6); this.ended = ended; + this.weekDays = []; + let dayIndex = new Date(started.getTime()); + + while (dayIndex < ended) { + this.weekDays.push({ + dated: new Date(dayIndex.getTime()) + }); + dayIndex.setDate(dayIndex.getDate() + 1); + } + this.fetchHours(); } @@ -49,23 +62,15 @@ class Controller { set hours(value) { this._hours = value; - this.weekDays = []; - if (!this.hours) return; - let dayIndex = new Date(this.started.getTime()); - - while (dayIndex < this.ended) { - let weekDay = dayIndex.getDay(); - - let hours = this.hours - .filter(hour => new Date(hour.timed).getDay() == weekDay) - .sort((a, b) => new Date(a.timed) - new Date(b.timed)); - - this.weekDays.push({ - dated: new Date(dayIndex.getTime()), - hours - }); - dayIndex.setDate(dayIndex.getDate() + 1); + for (const weekDay of this.weekDays) { + if (value) { + let day = weekDay.dated.getDay(); + weekDay.hours = value + .filter(hour => new Date(hour.timed).getDay() == day) + .sort((a, b) => new Date(a.timed) - new Date(b.timed)); + } else + weekDay.hours = null; } } @@ -74,61 +79,89 @@ class Controller { const filter = { where: {and: [ {timed: {gte: this.started}}, - {timed: {lt: this.ended}} + {timed: {lte: this.ended}} ]} }; - this.$.model.applyFilter(filter, params); + + this.getAbsences(); + this.getWorkedHours(this.started, this.ended); } hasEvents(day) { return day >= this.started && day < this.ended; } - hourColor(weekDay) { - return weekDay.manual ? 'alert' : 'warning'; + getWorkedHours(from, to) { + let params = { + id: this.$stateParams.id, + from: from, + to: to + }; + + const query = `api/workers/${this.$stateParams.id}/getWorkedHours`; + return this.$http.get(query, {params}).then(res => { + const workDays = res.data; + const map = new Map(); + + for (const workDay of workDays) { + workDay.dated = new Date(workDay.dated); + map.set(workDay.dated, workDay); + } + + for (const weekDay of this.weekDays) { + const workDay = workDays.find(day => { + let from = new Date(day.dated); + from.setHours(0, 0, 0, 0); + + let to = new Date(day.dated); + to.setHours(23, 59, 59, 59); + + return weekDay.dated >= from && weekDay.dated <= to; + }); + weekDay.expectedHours = workDay.expectedHours; + weekDay.workedHours = workDay.workedHours; + } + }); } - getWeekdayTotalHours(weekday) { - if (weekday.hours.length == 0) return 0; + getFinishTime() { + let weekOffset = new Date().getDay() - 1; + if (weekOffset < 0) weekOffset = 6; + const today = this.weekDays[weekOffset]; - const hours = weekday.hours; + if (today && today.workedHours) { + const remainingTime = (today.expectedHours - today.workedHours) * 1000; + const lastKnownTime = new Date(today.hours[today.hours.length - 1].timed).getTime(); + const finishTimeStamp = lastKnownTime + remainingTime; - let totalStamp = 0; + let finishDate = new Date(finishTimeStamp); + let hour = finishDate.getHours(); + let minute = finishDate.getMinutes(); - hours.forEach((hour, index) => { - let currentHour = new Date(hour.timed); - let previousHour = new Date(hour.timed); + if (hour < 10) hour = `0${hour}`; + if (minute < 10) minute = `0${minute}`; - if (index > 0 && (index % 2 == 1)) - previousHour = new Date(hours[index - 1].timed); - - const dif = Math.abs(previousHour - currentHour); - - totalStamp += dif; - }); - - if (totalStamp / 3600 / 1000 > 5) - totalStamp += (20 * 60 * 1000); - - weekday.total = totalStamp; - - return this.formatHours(totalStamp); + return `${hour}:${minute} h.`; + } } get weekTotalHours() { let total = 0; this.weekDays.forEach(weekday => { - if (weekday.total) - total += weekday.total; + if (weekday.workedHours) + total += weekday.workedHours; }); + return this.formatHours(total); } formatHours(timestamp) { - let hour = Math.floor(timestamp / 3600 / 1000); - let min = Math.floor(timestamp / 60 / 1000 - 60 * hour); + timestamp = timestamp || 0; + + let hour = Math.floor(timestamp / 3600); + let min = Math.floor(timestamp / 60 - 60 * hour); if (hour < 10) hour = `0${hour}`; if (min < 10) min = `0${min}`; @@ -159,12 +192,75 @@ class Controller { workerFk: this.$stateParams.id, timed: this.newTime }; - this.$http.post(`api/WorkerTimeControls/addTime`, data) + this.$http.post(`api/WorkerTimeControls/addTimeEntry`, data) .then(() => this.fetchHours()); } + + showDeleteDialog(hour) { + this.timeEntryToDelete = hour; + this.$.deleteEntryDialog.show(); + } + + deleteTimeEntry(response) { + if (response !== 'ACCEPT') return; + + const entryId = this.timeEntryToDelete.id; + + this.$http.post(`api/WorkerTimeControls/${entryId}/deleteTimeEntry`).then(() => { + this.fetchHours(); + this.vnApp.showSuccess(this.$translate.instant('Entry removed')); + }); + } + + getAbsences() { + let params = { + workerFk: this.$stateParams.id, + started: this.started, + ended: this.ended + }; + + return this.$http.get(`api/WorkerCalendars/absences`, {params}) + .then(res => this.onData(res.data)); + } + + onData(data) { + const events = {}; + + let addEvent = (day, event) => { + events[new Date(day).getTime()] = event; + }; + + if (data.holidays) { + data.holidays.forEach(holiday => { + const holidayDetail = holiday.detail && holiday.detail.description; + const holidayType = holiday.type && holiday.type.name; + const holidayName = holidayDetail || holidayType; + + addEvent(holiday.dated, { + name: holidayName, + color: '#ff0' + }); + }); + } + if (data.absences) { + data.absences.forEach(absence => { + const type = absence.absenceType; + addEvent(absence.dated, { + name: type.name, + color: type.rgb + }); + }); + } + + this.weekDays.forEach(day => { + const timestamp = day.dated.getTime(); + if (events[timestamp]) + day.event = events[timestamp]; + }); + } } -Controller.$inject = ['$scope', '$http', '$stateParams', '$element', 'vnWeekDays']; +Controller.$inject = ['$scope', '$http', '$stateParams', '$element', 'vnWeekDays', 'vnApp', '$translate']; ngModule.component('vnWorkerTimeControl', { template: require('./index.html'), diff --git a/modules/worker/front/time-control/locale/es.yml b/modules/worker/front/time-control/locale/es.yml index 9a3484fc6..8ddab11a1 100644 --- a/modules/worker/front/time-control/locale/es.yml +++ b/modules/worker/front/time-control/locale/es.yml @@ -4,4 +4,7 @@ Hour: Hora Hours: Horas Add time: Añadir hora Week total: Total semana -Current week: Semana actual \ No newline at end of file +Current week: Semana actual +This time entry will be deleted: Se borrará la hora fichada +Are you sure you want to delete this entry?: ¿Seguro que quieres eliminarla? +Finish at: Termina a las \ No newline at end of file diff --git a/modules/worker/front/time-control/style.scss b/modules/worker/front/time-control/style.scss index 978a600a2..ba0ea4d64 100644 --- a/modules/worker/front/time-control/style.scss +++ b/modules/worker/front/time-control/style.scss @@ -1,7 +1,7 @@ @import "variables"; vn-worker-time-control { - vn-thead > vn-tr > vn-td > div { + vn-thead > vn-tr > vn-td > div.weekday { margin-bottom: 5px; color: $color-main } From 76f3dc48e590b975852666f9cee418a5af8035a0 Mon Sep 17 00:00:00 2001 From: Carlos Jimenez Ruiz Date: Tue, 5 Nov 2019 11:57:05 +0100 Subject: [PATCH 02/41] #1852 worker.time-control --- front/core/components/chip/style.scss | 9 +--- modules/worker/front/calendar/index.html | 6 --- modules/worker/front/time-control/index.html | 20 +++----- modules/worker/front/time-control/index.js | 51 +++++++++---------- .../worker/front/time-control/locale/es.yml | 3 +- modules/worker/front/time-control/style.scss | 1 + 6 files changed, 35 insertions(+), 55 deletions(-) diff --git a/front/core/components/chip/style.scss b/front/core/components/chip/style.scss index 31ea01a14..5840ecdf1 100644 --- a/front/core/components/chip/style.scss +++ b/front/core/components/chip/style.scss @@ -8,11 +8,7 @@ vn-chip { margin: .25em; display: inline-flex; align-items: center; - text-overflow: ellipsis; - white-space: nowrap; height: 2em; - padding: 0 .7em; - overflow: hidden; max-width: 100%; box-sizing: border-box; @@ -28,13 +24,12 @@ vn-chip { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; - line-height: 2em; & > vn-avatar { margin-left: -0.7em; margin-right: .3em; vertical-align: middle; - height: 1.9em; + height: 2em; width: 2em; } } @@ -55,8 +50,6 @@ vn-chip { vn-avatar { display: inline-block; - height: 2em; - width: 2em; min-width: 2em; border-radius: 50%; } \ No newline at end of file diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index bcf4c44f2..d3eb2ad67 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -34,12 +34,6 @@ {{absenceType.name}} - - - - My longer overflowing absence type in chip -
\ No newline at end of file diff --git a/modules/worker/front/time-control/index.html b/modules/worker/front/time-control/index.html index 84ff963e2..0ff0ef393 100644 --- a/modules/worker/front/time-control/index.html +++ b/modules/worker/front/time-control/index.html @@ -36,14 +36,10 @@
@@ -78,18 +74,16 @@
-
- - -
Hours
+ +
\ No newline at end of file diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index a8d6a742e..30c8d1604 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -1,16 +1,11 @@ +import Component from 'core/lib/component'; import ngModule from '../module'; import './style.scss'; - -class Controller { - constructor($scope, $http, $stateParams, $element, vnWeekDays, vnApp, $translate) { - this.$stateParams = $stateParams; - this.$ = $scope; - this.$http = $http; - this.$element = $element; +class Controller extends Component { + constructor($element, $, vnWeekDays) { + super($element, $); this.weekDays = []; this.weekdayNames = vnWeekDays.locales; - this.vnApp = vnApp; - this.$translate = $translate; } $postLink() { @@ -75,7 +70,7 @@ class Controller { } fetchHours() { - const params = {workerFk: this.$stateParams.id}; + const params = {workerFk: this.$params.id}; const filter = { where: {and: [ {timed: {gte: this.started}}, @@ -94,12 +89,12 @@ class Controller { getWorkedHours(from, to) { let params = { - id: this.$stateParams.id, + id: this.$params.id, from: from, to: to }; - const query = `api/workers/${this.$stateParams.id}/getWorkedHours`; + const query = `Workers/${this.$params.id}/getWorkedHours`; return this.$http.get(query, {params}).then(res => { const workDays = res.data; const map = new Map(); @@ -126,13 +121,16 @@ class Controller { } getFinishTime() { - let weekOffset = new Date().getDay() - 1; - if (weekOffset < 0) weekOffset = 6; - const today = this.weekDays[weekOffset]; + if (!this.weekDays) return; - if (today && today.workedHours) { - const remainingTime = (today.expectedHours - today.workedHours) * 1000; - const lastKnownTime = new Date(today.hours[today.hours.length - 1].timed).getTime(); + let today = new Date(); + today.setHours(0, 0, 0, 0); + + let todayInWeek = this.weekDays.find(day => day.dated.getTime() === today.getTime()); + + if (todayInWeek && todayInWeek.hours) { + const remainingTime = (todayInWeek.expectedHours - todayInWeek.workedHours) * 1000; + const lastKnownTime = new Date(todayInWeek.hours[todayInWeek.hours.length - 1].timed).getTime(); const finishTimeStamp = lastKnownTime + remainingTime; let finishDate = new Date(finishTimeStamp); @@ -147,6 +145,7 @@ class Controller { } get weekTotalHours() { + if (!this.weekDays) return 0; let total = 0; this.weekDays.forEach(weekday => { @@ -181,7 +180,7 @@ class Controller { addTime(response) { if (response !== 'accept') return; let data = { - workerFk: this.$stateParams.id, + workerFk: this.$params.id, timed: this.newTime }; this.$http.post(`WorkerTimeControls/addTimeEntry`, data) @@ -193,25 +192,23 @@ class Controller { this.$.deleteEntryDialog.show(); } - deleteTimeEntry(response) { - if (response !== 'ACCEPT') return; - + deleteTimeEntry() { const entryId = this.timeEntryToDelete.id; - this.$http.post(`api/WorkerTimeControls/${entryId}/deleteTimeEntry`).then(() => { + this.$http.post(`WorkerTimeControls/${entryId}/deleteTimeEntry`).then(() => { this.fetchHours(); - this.vnApp.showSuccess(this.$translate.instant('Entry removed')); + this.vnApp.showSuccess(this.$t('Entry removed')); }); } getAbsences() { let params = { - workerFk: this.$stateParams.id, + workerFk: this.$params.id, started: this.started, ended: this.ended }; - return this.$http.get(`api/WorkerCalendars/absences`, {params}) + return this.$http.get(`WorkerCalendars/absences`, {params}) .then(res => this.onData(res.data)); } @@ -252,7 +249,7 @@ class Controller { } } -Controller.$inject = ['$scope', '$http', '$stateParams', '$element', 'vnWeekDays', 'vnApp', '$translate']; +Controller.$inject = ['$element', '$scope', 'vnWeekDays']; ngModule.component('vnWorkerTimeControl', { template: require('./index.html'), diff --git a/modules/worker/front/time-control/locale/es.yml b/modules/worker/front/time-control/locale/es.yml index 8ddab11a1..2f92657dd 100644 --- a/modules/worker/front/time-control/locale/es.yml +++ b/modules/worker/front/time-control/locale/es.yml @@ -7,4 +7,5 @@ Week total: Total semana Current week: Semana actual This time entry will be deleted: Se borrará la hora fichada Are you sure you want to delete this entry?: ¿Seguro que quieres eliminarla? -Finish at: Termina a las \ No newline at end of file +Finish at: Termina a las +Entry removed: Fichada borrada \ No newline at end of file diff --git a/modules/worker/front/time-control/style.scss b/modules/worker/front/time-control/style.scss index ba0ea4d64..5d82c6d31 100644 --- a/modules/worker/front/time-control/style.scss +++ b/modules/worker/front/time-control/style.scss @@ -6,6 +6,7 @@ vn-worker-time-control { color: $color-main } vn-td.hours { + min-width: 100px; vertical-align: top; & > section { From 515234187a18832f68e26030cf240a89944b9b55 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 10 Nov 2019 11:08:44 +0100 Subject: [PATCH 03/41] First working version --- e2e/helpers/extensions.js | 2 +- e2e/helpers/selectors.js | 22 +- .../02-client-module/01_create_client.spec.js | 8 +- e2e/paths/02-client-module/14_balance.spec.js | 8 +- e2e/paths/04-item-module/01_summary.spec.js | 18 +- .../08_create_and_clone.spec.js | 16 +- .../04-item-module/09_regularize.spec.js | 32 +- .../04-item-module/10_item_index.spec.js | 8 +- e2e/paths/04-item-module/11_item_log.spec.js | 4 +- .../01-sale/02_edit_sale.spec.js | 4 +- e2e/paths/05-ticket-module/09_weekly.spec.js | 12 +- e2e/paths/05-ticket-module/11_diary.spec.js | 8 +- .../05-ticket-module/12_descriptor.spec.js | 22 +- .../06-claim-module/06_descriptor.spec.js | 4 +- .../01_descriptor.spec.js | 16 +- front/core/components/autocomplete/index.js | 3 +- front/core/components/button-menu/index.js | 5 +- front/core/components/button/style.scss | 6 +- front/core/components/check/style.scss | 2 +- front/core/components/date-picker/index.js | 4 +- front/core/components/dialog/index.js | 12 +- front/core/components/dialog/index.spec.js | 19 +- front/core/components/dialog/style.scss | 2 +- front/core/components/drop-down/index.js | 17 +- front/core/components/field/index.html | 3 +- front/core/components/field/style.scss | 47 ++- front/core/components/icon-button/style.scss | 2 +- front/core/components/index.js | 1 + front/core/components/list/style.scss | 119 +++++-- front/core/components/popup/index.js | 23 +- front/core/components/radio/style.scss | 4 +- front/core/components/range/style.scss | 8 +- .../core/components/searchbar/searchbar.html | 16 +- front/core/components/searchbar/searchbar.js | 302 ++++++++++-------- front/core/components/searchbar/style.scss | 35 +- front/core/components/slot/index.js | 81 +++++ front/core/components/slot/portal.js | 28 ++ front/core/components/slot/style.scss | 3 + front/core/components/spinner/spinner.html | 1 + front/core/components/spinner/style.scss | 2 +- front/core/components/table/style.scss | 2 +- front/core/components/toggle/style.scss | 4 +- front/core/components/wday-picker/style.scss | 2 +- front/core/directives/id.js | 17 +- front/core/lib/component.js | 3 + front/core/lib/focus.js | 16 + front/core/lib/index.js | 3 +- front/core/module.js | 23 +- front/core/services/interceptor.js | 6 +- front/core/styles/global.scss | 4 + front/core/styles/variables.scss | 29 +- front/core/vendor.js | 2 + front/package-lock.json | 5 + front/package.json | 1 + front/salix/components/app/app.html | 29 +- front/salix/components/app/app.js | 25 +- front/salix/components/app/style.scss | 98 +----- front/salix/components/descriptor/style.scss | 2 +- front/salix/components/home/home.html | 2 +- front/salix/components/home/style.scss | 2 +- front/salix/components/index.js | 5 +- front/salix/components/layout/index.html | 69 ++++ front/salix/components/layout/index.js | 19 ++ .../salix/components/{app => layout}/logo.svg | 0 front/salix/components/layout/style.scss | 121 +++++++ .../salix/components/left-menu/left-menu.html | 31 +- front/salix/components/left-menu/left-menu.js | 44 +-- front/salix/components/left-menu/style.scss | 51 +-- front/salix/components/login/login.js | 2 + .../salix/components/main-menu/main-menu.html | 38 +-- front/salix/components/main-menu/main-menu.js | 7 +- front/salix/components/main-menu/style.scss | 51 ++- front/salix/components/module-card/index.js | 29 ++ front/salix/components/module-card/style.scss | 5 + front/salix/components/module-main/index.html | 4 + front/salix/components/module-main/index.js | 15 + front/salix/components/module-main/style.scss | 5 + front/salix/components/side-menu/side-menu.js | 38 ++- front/salix/components/side-menu/style.scss | 8 +- front/salix/components/summary/style.scss | 1 + front/salix/components/topbar/style.scss | 9 +- front/salix/components/topbar/topbar.js | 6 - .../salix/components/user-popover/style.scss | 2 +- front/salix/module.js | 7 +- front/salix/routes.js | 63 ++-- front/salix/styles/misc.scss | 15 +- modules/agency/front/basic-data/index.html | 11 +- modules/agency/front/card/index.html | 8 +- modules/agency/front/card/index.js | 29 +- modules/agency/front/card/index.spec.js | 33 +- modules/agency/front/create/index.html | 155 ++++----- modules/agency/front/delivery-days/index.html | 9 +- modules/agency/front/events/index.html | 109 +++---- modules/agency/front/index/index.html | 105 +++--- modules/agency/front/location/index.html | 39 +-- modules/agency/front/location/index.js | 9 +- modules/agency/front/location/style.scss | 6 +- modules/agency/front/main/index.html | 25 +- modules/agency/front/main/index.js | 7 +- modules/agency/front/main/style.scss | 18 -- modules/agency/front/routes.json | 18 +- modules/agency/front/search-panel/index.html | 4 +- modules/claim/front/basic-data/index.html | 2 +- modules/claim/front/card/index.html | 12 +- modules/claim/front/card/index.js | 43 +-- modules/claim/front/card/index.spec.js | 26 +- modules/claim/front/development/index.html | 2 +- modules/claim/front/index.js | 1 + modules/claim/front/index/index.html | 113 +++---- modules/claim/front/main/index.html | 11 + modules/claim/front/main/index.js | 9 + modules/claim/front/routes.json | 21 +- .../client/front/address/create/index.html | 2 +- modules/client/front/address/edit/index.html | 2 +- modules/client/front/billing-data/index.html | 2 +- modules/client/front/card/index.html | 12 +- modules/client/front/card/index.js | 24 +- modules/client/front/card/index.spec.js | 24 +- modules/client/front/contact/index.html | 2 +- modules/client/front/create/index.html | 295 ++++++++--------- .../front/credit-insurance/create/index.html | 2 +- .../insurance/create/index.html | 2 +- modules/client/front/credit/create/index.html | 2 +- modules/client/front/dms/create/index.html | 4 +- modules/client/front/dms/edit/index.html | 2 +- modules/client/front/fiscal-data/index.html | 2 +- modules/client/front/greuge/create/index.html | 2 +- modules/client/front/index.js | 1 + modules/client/front/index/index.html | 105 +++--- modules/client/front/index/index.js | 13 +- modules/client/front/main/index.html | 11 + modules/client/front/main/index.js | 9 + modules/client/front/note/create/index.html | 2 +- .../client/front/recovery/create/index.html | 2 +- modules/client/front/routes.json | 57 ++-- modules/client/front/sample/create/index.html | 2 +- modules/client/front/web-access/index.html | 2 +- modules/invoiceOut/front/card/index.html | 12 +- modules/invoiceOut/front/card/index.js | 27 +- modules/invoiceOut/front/index.js | 1 + modules/invoiceOut/front/index/index.html | 123 ++++--- modules/invoiceOut/front/main/index.html | 11 + modules/invoiceOut/front/main/index.js | 9 + modules/invoiceOut/front/routes.json | 7 +- modules/item/front/barcode/index.html | 2 +- modules/item/front/basic-data/index.html | 2 +- modules/item/front/botanical/index.html | 2 +- modules/item/front/card/index.html | 12 +- modules/item/front/card/index.js | 24 +- modules/item/front/card/index.spec.js | 29 +- modules/item/front/create/index.html | 114 ++++--- modules/item/front/diary/index.html | 2 +- modules/item/front/index.js | 1 + modules/item/front/index/index.html | 226 ++++++------- modules/item/front/index/index.js | 16 - modules/item/front/main/index.html | 12 + modules/item/front/main/index.js | 9 + modules/item/front/niche/index.html | 2 +- modules/item/front/request/index.html | 212 ++++++------ modules/item/front/routes.json | 30 +- modules/item/front/tags/index.html | 2 +- modules/item/front/tax/index.html | 2 +- modules/order/front/basic-data/index.html | 2 +- modules/order/front/card/index.html | 12 +- modules/order/front/card/index.js | 72 ++--- modules/order/front/card/index.spec.js | 60 +--- modules/order/front/catalog/index.html | 3 +- modules/order/front/create/index.html | 18 +- modules/order/front/descriptor/index.js | 1 + modules/order/front/index.js | 1 + modules/order/front/index/index.html | 131 ++++---- modules/order/front/main/index.html | 11 + modules/order/front/main/index.js | 9 + modules/order/front/routes.json | 19 +- modules/route/front/basic-data/index.html | 2 +- modules/route/front/card/index.html | 12 +- modules/route/front/card/index.js | 35 +- modules/route/front/create/index.html | 104 +++--- modules/route/front/descriptor/index.js | 4 +- modules/route/front/index.js | 1 + modules/route/front/index/index.html | 104 +++--- modules/route/front/index/index.js | 30 +- modules/route/front/index/style.scss | 5 - modules/route/front/main/index.html | 12 + modules/route/front/main/index.js | 26 ++ modules/route/front/routes.json | 16 +- modules/ticket/front/basic-data/index.html | 24 +- modules/ticket/front/card/index.html | 12 +- modules/ticket/front/card/index.js | 57 ++-- modules/ticket/front/card/index.spec.js | 37 +-- modules/ticket/front/create/index.html | 18 +- modules/ticket/front/dms/create/index.html | 4 +- modules/ticket/front/dms/edit/index.html | 4 +- modules/ticket/front/index.js | 1 + modules/ticket/front/index/index.html | 274 ++++++++-------- modules/ticket/front/index/index.js | 15 +- modules/ticket/front/main/index.html | 11 + modules/ticket/front/main/index.js | 9 + modules/ticket/front/note/index.html | 2 +- .../ticket/front/request/create/index.html | 2 +- modules/ticket/front/routes.json | 42 +-- modules/ticket/front/sale/index.html | 4 +- modules/ticket/front/tracking/edit/index.html | 2 +- modules/ticket/front/weekly/create/index.html | 126 ++++---- modules/ticket/front/weekly/index/index.html | 146 +++++---- modules/travel/front/basic-data/index.html | 2 +- modules/travel/front/card/index.html | 12 +- modules/travel/front/card/index.js | 31 +- modules/travel/front/descriptor/index.js | 2 +- modules/travel/front/index.js | 1 + modules/travel/front/index/index.html | 103 +++--- modules/travel/front/main/index.html | 11 + modules/travel/front/main/index.js | 9 + modules/travel/front/routes.json | 15 +- modules/worker/front/account/index.html | 2 +- modules/worker/front/basic-data/index.html | 2 +- modules/worker/front/calendar/index.html | 26 +- modules/worker/front/card/index.html | 12 +- modules/worker/front/card/index.js | 25 +- modules/worker/front/department/index.html | 37 +-- modules/worker/front/index.js | 1 + modules/worker/front/index/index.html | 96 +++--- modules/worker/front/index/index.js | 6 +- modules/worker/front/main/index.html | 11 + modules/worker/front/main/index.js | 9 + modules/worker/front/pbx/index.html | 2 +- modules/worker/front/phones/index.html | 2 +- modules/worker/front/routes.json | 40 +-- modules/worker/front/time-control/index.html | 120 ++++--- 229 files changed, 3278 insertions(+), 2925 deletions(-) create mode 100644 front/core/components/slot/index.js create mode 100644 front/core/components/slot/portal.js create mode 100644 front/core/components/slot/style.scss create mode 100644 front/core/lib/focus.js create mode 100644 front/salix/components/layout/index.html create mode 100644 front/salix/components/layout/index.js rename front/salix/components/{app => layout}/logo.svg (100%) create mode 100644 front/salix/components/layout/style.scss create mode 100644 front/salix/components/module-card/index.js create mode 100644 front/salix/components/module-card/style.scss create mode 100644 front/salix/components/module-main/index.html create mode 100644 front/salix/components/module-main/index.js create mode 100644 front/salix/components/module-main/style.scss delete mode 100644 modules/agency/front/main/style.scss create mode 100644 modules/claim/front/main/index.html create mode 100644 modules/claim/front/main/index.js create mode 100644 modules/client/front/main/index.html create mode 100644 modules/client/front/main/index.js create mode 100644 modules/invoiceOut/front/main/index.html create mode 100644 modules/invoiceOut/front/main/index.js create mode 100644 modules/item/front/main/index.html create mode 100644 modules/item/front/main/index.js create mode 100644 modules/order/front/main/index.html create mode 100644 modules/order/front/main/index.js delete mode 100644 modules/route/front/index/style.scss create mode 100644 modules/route/front/main/index.html create mode 100644 modules/route/front/main/index.js create mode 100644 modules/ticket/front/main/index.html create mode 100644 modules/ticket/front/main/index.js create mode 100644 modules/travel/front/main/index.html create mode 100644 modules/travel/front/main/index.js create mode 100644 modules/worker/front/main/index.html create mode 100644 modules/worker/front/main/index.js diff --git a/e2e/helpers/extensions.js b/e2e/helpers/extensions.js index 46c43a254..e46ad79ba 100644 --- a/e2e/helpers/extensions.js +++ b/e2e/helpers/extensions.js @@ -346,7 +346,7 @@ let actions = { .write('vn-searchbar input', searchValue) .click('vn-searchbar vn-icon[icon="search"]') .wait(100) - .waitForNumberOfElements('.searchResult', 1) + .waitForNumberOfElements('.search-result', 1) .evaluate(() => { return document.querySelector('ui-view vn-card vn-table') != null; }) diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index dc6083b39..52e6e256b 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -23,7 +23,7 @@ export default { clientsIndex: { searchClientInput: `vn-textfield input`, searchButton: 'vn-searchbar vn-icon[icon="search"]', - searchResult: 'vn-client-index .vn-list-item', + search-result: 'vn-client-index .vn-item', createClientButton: `vn-float-button`, othersButton: 'vn-left-menu li[name="Others"] > a' }, @@ -182,9 +182,9 @@ export default { itemsIndex: { searchIcon: 'vn-item-index vn-searchbar vn-icon[icon="search"]', createItemButton: `vn-float-button`, - searchResult: 'vn-item-index a.vn-tr', - searchResultPreviewButton: 'vn-item-index .buttons > [icon="desktop_windows"]', - searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]', + search-result: 'vn-item-index a.vn-tr', + search-resultPreviewButton: 'vn-item-index .buttons > [icon="desktop_windows"]', + search-resultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]', acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]', searchItemInput: 'vn-searchbar vn-textfield input', searchButton: 'vn-searchbar vn-icon[icon="search"]', @@ -326,9 +326,9 @@ export default { openAdvancedSearchButton: 'vn-ticket-index vn-searchbar .append vn-icon[icon="arrow_drop_down"]', advancedSearchInvoiceOut: 'vn-ticket-search-panel vn-textfield[ng-model="filter.refFk"] input', newTicketButton: 'vn-ticket-index > a', - searchResult: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr', + search-result: 'vn-ticket-index vn-card > vn-table > div > vn-tbody > a.vn-tr', searchWeeklyResult: 'vn-ticket-weekly-index vn-table vn-tbody > vn-tr', - searchResultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', + search-resultDate: 'vn-ticket-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(5)', searchTicketInput: `vn-ticket-index vn-textfield input`, searchWeeklyTicketInput: `vn-ticket-weekly-index vn-textfield input`, searchWeeklyClearInput: 'vn-ticket-weekly-index vn-searchbar vn-icon[icon=clear]', @@ -517,7 +517,7 @@ export default { }, claimsIndex: { searchClaimInput: `vn-claim-index vn-textfield input`, - searchResult: 'vn-claim-index vn-card > vn-table > div > vn-tbody > a', + search-result: 'vn-claim-index vn-card > vn-table > div > vn-tbody > a', searchButton: 'vn-claim-index vn-searchbar vn-icon[icon="search"]' }, claimDescriptor: { @@ -580,9 +580,9 @@ export default { isPaidWithManaCheckbox: 'vn-check[ng-model="$ctrl.claim.isChargedToMana"]' }, ordersIndex: { - searchResult: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr', - searchResultDate: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)', - searchResultAddress: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(6)', + search-result: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr', + search-resultDate: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)', + search-resultAddress: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(6)', searchOrderInput: `vn-order-index vn-textfield input`, searchButton: 'vn-order-index vn-searchbar vn-icon[icon="search"]', createOrderButton: `vn-float-button`, @@ -722,7 +722,7 @@ export default { invoiceOutIndex: { searchInvoiceOutInput: `vn-invoice-out-index vn-textfield input`, searchButton: 'vn-invoice-out-index vn-searchbar vn-icon[icon="search"]', - searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr', + search-result: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr', }, invoiceOutDescriptor: { moreMenu: 'vn-invoice-out-descriptor vn-icon-menu[icon=more_vert]', diff --git a/e2e/paths/02-client-module/01_create_client.spec.js b/e2e/paths/02-client-module/01_create_client.spec.js index f2fe30464..abac46535 100644 --- a/e2e/paths/02-client-module/01_create_client.spec.js +++ b/e2e/paths/02-client-module/01_create_client.spec.js @@ -13,8 +13,8 @@ describe('Client create path', () => { const result = await nightmare .write(selectors.clientsIndex.searchClientInput, 'Carol Danvers') .waitToClick(selectors.clientsIndex.searchButton) - .waitForNumberOfElements(selectors.clientsIndex.searchResult, 0) - .countElement(selectors.clientsIndex.searchResult); + .waitForNumberOfElements(selectors.clientsIndex.search-result, 0) + .countElement(selectors.clientsIndex.search-result); expect(result).toEqual(0); }); @@ -117,8 +117,8 @@ describe('Client create path', () => { const result = await nightmare .write(selectors.clientsIndex.searchClientInput, 'Carol Danvers') .waitToClick(selectors.clientsIndex.searchButton) - .waitForNumberOfElements(selectors.clientsIndex.searchResult, 1) - .countElement(selectors.clientsIndex.searchResult); + .waitForNumberOfElements(selectors.clientsIndex.search-result, 1) + .countElement(selectors.clientsIndex.search-result); expect(result).toEqual(1); }); diff --git a/e2e/paths/02-client-module/14_balance.spec.js b/e2e/paths/02-client-module/14_balance.spec.js index b661d998b..e056b5eea 100644 --- a/e2e/paths/02-client-module/14_balance.spec.js +++ b/e2e/paths/02-client-module/14_balance.spec.js @@ -135,16 +135,16 @@ describe('Client balance path', () => { let resultCount = await nightmare .write(selectors.clientsIndex.searchClientInput, 'Petter Parker') .waitToClick(selectors.clientsIndex.searchButton) - .waitForNumberOfElements(selectors.clientsIndex.searchResult, 1) - .countElement(selectors.clientsIndex.searchResult); + .waitForNumberOfElements(selectors.clientsIndex.search-result, 1) + .countElement(selectors.clientsIndex.search-result); expect(resultCount).toEqual(1); }); it(`should click on the search result to access to the client's balance`, async() => { let url = await nightmare - .waitForTextInElement(selectors.clientsIndex.searchResult, 'Petter Parker') - .waitToClick(selectors.clientsIndex.searchResult) + .waitForTextInElement(selectors.clientsIndex.search-result, 'Petter Parker') + .waitToClick(selectors.clientsIndex.search-result) .waitToClick(selectors.clientBalance.balanceButton) .waitForURL('/balance') .parsedUrl(); diff --git a/e2e/paths/04-item-module/01_summary.spec.js b/e2e/paths/04-item-module/01_summary.spec.js index bd06ebef4..41737a7e6 100644 --- a/e2e/paths/04-item-module/01_summary.spec.js +++ b/e2e/paths/04-item-module/01_summary.spec.js @@ -14,16 +14,16 @@ describe('Item summary path', () => { .clearInput(selectors.itemsIndex.searchItemInput) .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon longbow 2m') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 1) + .countElement(selectors.itemsIndex.search-result); expect(result).toEqual(1); }); it(`should click on the search result summary button to open the item summary popup`, async() => { const isVisible = await nightmare - .waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon longbow 2m') - .waitToClick(selectors.itemsIndex.searchResultPreviewButton) + .waitForTextInElement(selectors.itemsIndex.search-result, 'Ranged weapon longbow 2m') + .waitToClick(selectors.itemsIndex.search-resultPreviewButton) .isVisible(selectors.itemSummary.basicData); expect(isVisible).toBeTruthy(); @@ -84,16 +84,16 @@ describe('Item summary path', () => { .waitToClick(selectors.itemsIndex.searchButton) .write(selectors.itemsIndex.searchItemInput, 'Melee weapon combat fist 15cm') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 1) + .countElement(selectors.itemsIndex.search-result); expect(result).toEqual(1); }); it(`should now click on the search result summary button to open the item summary popup`, async() => { const isVisible = await nightmare - .waitForTextInElement(selectors.itemsIndex.searchResult, 'Melee weapon combat fist 15cm') - .waitToClick(selectors.itemsIndex.searchResultPreviewButton) + .waitForTextInElement(selectors.itemsIndex.search-result, 'Melee weapon combat fist 15cm') + .waitToClick(selectors.itemsIndex.search-resultPreviewButton) .isVisible(selectors.itemSummary.basicData); @@ -151,7 +151,7 @@ describe('Item summary path', () => { it(`should navigate to the one of the items detailed section`, async() => { const url = await nightmare - .waitToClick(selectors.itemsIndex.searchResult) + .waitToClick(selectors.itemsIndex.search-result) .waitForURL('summary') .parsedUrl(); diff --git a/e2e/paths/04-item-module/08_create_and_clone.spec.js b/e2e/paths/04-item-module/08_create_and_clone.spec.js index 8f4952fb1..0fe8596c3 100644 --- a/e2e/paths/04-item-module/08_create_and_clone.spec.js +++ b/e2e/paths/04-item-module/08_create_and_clone.spec.js @@ -14,8 +14,8 @@ describe('Item Create/Clone path', () => { .clearInput(selectors.itemsIndex.searchItemInput) .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 0) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 0) + .countElement(selectors.itemsIndex.search-result); expect(result).toEqual(0); }); @@ -99,16 +99,16 @@ describe('Item Create/Clone path', () => { .clearInput(selectors.itemsIndex.searchItemInput) .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 1) + .countElement(selectors.itemsIndex.search-result); expect(result).toEqual(1); }); it(`should clone the Infinity Gauntlet`, async() => { const url = await nightmare - .waitForTextInElement(selectors.itemsIndex.searchResult, 'Infinity Gauntlet') - .waitToClick(selectors.itemsIndex.searchResultCloneButton) + .waitForTextInElement(selectors.itemsIndex.search-result, 'Infinity Gauntlet') + .waitToClick(selectors.itemsIndex.search-resultCloneButton) .waitToClick(selectors.itemsIndex.acceptClonationAlertButton) .waitForURL('tags') .parsedUrl(); @@ -122,8 +122,8 @@ describe('Item Create/Clone path', () => { .clearInput(selectors.itemsIndex.searchItemInput) .write(selectors.itemsIndex.searchItemInput, 'Infinity Gauntlet') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 2) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 2) + .countElement(selectors.itemsIndex.search-result); expect(result).toEqual(2); }); diff --git a/e2e/paths/04-item-module/09_regularize.spec.js b/e2e/paths/04-item-module/09_regularize.spec.js index 242414372..82da8b01a 100644 --- a/e2e/paths/04-item-module/09_regularize.spec.js +++ b/e2e/paths/04-item-module/09_regularize.spec.js @@ -32,16 +32,16 @@ describe('Item regularize path', () => { .clearInput(selectors.itemsIndex.searchItemInput) .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 1) + .countElement(selectors.itemsIndex.search-result); expect(resultCount).toEqual(1); }); it(`should click on the search result to access to the item tax`, async() => { const url = await nightmare - .waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon pistol 9mm') - .waitToClick(selectors.itemsIndex.searchResult) + .waitForTextInElement(selectors.itemsIndex.search-result, 'Ranged weapon pistol 9mm') + .waitToClick(selectors.itemsIndex.search-result) .waitForURL('/summary') .parsedUrl(); @@ -91,16 +91,16 @@ describe('Item regularize path', () => { const result = await nightmare .write(selectors.ticketsIndex.searchTicketInput, 'missing') .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); it(`should click on the search result to access to the ticket summary`, async() => { const url = await nightmare - .waitForTextInElement(selectors.ticketsIndex.searchResult, 'Missing') - .waitToClick(selectors.ticketsIndex.searchResult) + .waitForTextInElement(selectors.ticketsIndex.search-result, 'Missing') + .waitToClick(selectors.ticketsIndex.search-result) .waitForURL('/summary') .parsedUrl(); @@ -138,16 +138,16 @@ describe('Item regularize path', () => { .clearInput(selectors.itemsIndex.searchItemInput) .write(selectors.itemsIndex.searchItemInput, 'Ranged weapon pistol 9mm') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 1) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 1) + .countElement(selectors.itemsIndex.search-result); expect(resultCount).toEqual(1); }); it(`should click on the search result to access to the item tax`, async() => { const url = await nightmare - .waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon pistol 9mm') - .waitToClick(selectors.itemsIndex.searchResult) + .waitForTextInElement(selectors.itemsIndex.search-result, 'Ranged weapon pistol 9mm') + .waitToClick(selectors.itemsIndex.search-result) .waitForURL('/summary') .parsedUrl(); @@ -181,16 +181,16 @@ describe('Item regularize path', () => { const result = await nightmare .write(selectors.ticketsIndex.searchTicketInput, 25) .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); it(`should now click on the search result to access to the ticket summary`, async() => { const url = await nightmare - .waitForTextInElement(selectors.ticketsIndex.searchResult, '25') - .waitToClick(selectors.ticketsIndex.searchResult) + .waitForTextInElement(selectors.ticketsIndex.search-result, '25') + .waitToClick(selectors.ticketsIndex.search-result) .waitForURL('/summary') .parsedUrl(); diff --git a/e2e/paths/04-item-module/10_item_index.spec.js b/e2e/paths/04-item-module/10_item_index.spec.js index b83b37c59..13604063b 100644 --- a/e2e/paths/04-item-module/10_item_index.spec.js +++ b/e2e/paths/04-item-module/10_item_index.spec.js @@ -38,10 +38,10 @@ describe('Item index path', () => { it('should navigate forth and back to see the images column is still visible', async() => { const imageVisible = await nightmare - .waitToClick(selectors.itemsIndex.searchResult) + .waitToClick(selectors.itemsIndex.search-result) .waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton) .waitToClick(selectors.itemsIndex.searchIcon) - .wait(selectors.itemsIndex.searchResult) + .wait(selectors.itemsIndex.search-result) .isVisible(selectors.itemsIndex.firstItemImage); expect(imageVisible).toBeTruthy(); @@ -75,10 +75,10 @@ describe('Item index path', () => { it('should now navigate forth and back to see the ids column is now visible', async() => { const idVisible = await nightmare - .waitToClick(selectors.itemsIndex.searchResult) + .waitToClick(selectors.itemsIndex.search-result) .waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton) .waitToClick(selectors.itemsIndex.searchIcon) - .wait(selectors.itemsIndex.searchResult) + .wait(selectors.itemsIndex.search-result) .isVisible(selectors.itemsIndex.firstItemId); expect(idVisible).toBeTruthy(); diff --git a/e2e/paths/04-item-module/11_item_log.spec.js b/e2e/paths/04-item-module/11_item_log.spec.js index cf3beb528..d1cdfedea 100644 --- a/e2e/paths/04-item-module/11_item_log.spec.js +++ b/e2e/paths/04-item-module/11_item_log.spec.js @@ -12,8 +12,8 @@ describe('Item log path', () => { const result = await nightmare .write(selectors.itemsIndex.searchItemInput, 'Knowledge artifact') .waitToClick(selectors.itemsIndex.searchButton) - .waitForNumberOfElements(selectors.itemsIndex.searchResult, 0) - .countElement(selectors.itemsIndex.searchResult); + .waitForNumberOfElements(selectors.itemsIndex.search-result, 0) + .countElement(selectors.itemsIndex.search-result); expect(result).toEqual(0); }); diff --git a/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js index 5a6d6cd62..c1b9996c6 100644 --- a/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket-module/01-sale/02_edit_sale.spec.js @@ -213,8 +213,8 @@ xdescribe('Ticket Edit sale path', () => { const result = await nightmare .write(selectors.claimsIndex.searchClaimInput, 4) .waitToClick(selectors.claimsIndex.searchButton) - .waitForNumberOfElements(selectors.claimsIndex.searchResult, 1) - .countElement(selectors.claimsIndex.searchResult); + .waitForNumberOfElements(selectors.claimsIndex.search-result, 1) + .countElement(selectors.claimsIndex.search-result); expect(result).toEqual(1); }); diff --git a/e2e/paths/05-ticket-module/09_weekly.spec.js b/e2e/paths/05-ticket-module/09_weekly.spec.js index a7a301d79..f9f6a6380 100644 --- a/e2e/paths/05-ticket-module/09_weekly.spec.js +++ b/e2e/paths/05-ticket-module/09_weekly.spec.js @@ -34,15 +34,15 @@ describe('Ticket descriptor path', () => { const result = await nightmare .write(selectors.ticketsIndex.searchTicketInput, 11) .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); it(`should click on the search result to access to the ticket`, async() => { const url = await nightmare - .waitToClick(selectors.ticketsIndex.searchResult) + .waitToClick(selectors.ticketsIndex.search-result) .waitForURL('/summary') .parsedUrl(); @@ -94,15 +94,15 @@ describe('Ticket descriptor path', () => { const result = await nightmare .write(selectors.ticketsIndex.searchTicketInput, 11) .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); it(`should click on the search result to access to the ticket`, async() => { const url = await nightmare - .waitToClick(selectors.ticketsIndex.searchResult) + .waitToClick(selectors.ticketsIndex.search-result) .waitForURL('/summary') .parsedUrl(); diff --git a/e2e/paths/05-ticket-module/11_diary.spec.js b/e2e/paths/05-ticket-module/11_diary.spec.js index aeec780e7..bd086c527 100644 --- a/e2e/paths/05-ticket-module/11_diary.spec.js +++ b/e2e/paths/05-ticket-module/11_diary.spec.js @@ -14,16 +14,16 @@ describe('Ticket diary path', () => { const result = await nightmare .write(selectors.ticketsIndex.searchTicketInput, 1) .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); it(`should click on the search result to access to the ticket summary`, async() => { const url = await nightmare - .waitForTextInElement(selectors.ticketsIndex.searchResult, 'Bat cave') - .waitToClick(selectors.ticketsIndex.searchResult) + .waitForTextInElement(selectors.ticketsIndex.search-result, 'Bat cave') + .waitToClick(selectors.ticketsIndex.search-result) .waitForURL('/summary') .parsedUrl(); diff --git a/e2e/paths/05-ticket-module/12_descriptor.spec.js b/e2e/paths/05-ticket-module/12_descriptor.spec.js index b9c6c7c87..4f405dd37 100644 --- a/e2e/paths/05-ticket-module/12_descriptor.spec.js +++ b/e2e/paths/05-ticket-module/12_descriptor.spec.js @@ -14,16 +14,16 @@ describe('Ticket descriptor path', () => { const result = await nightmare .write(selectors.ticketsIndex.searchTicketInput, 18) .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); it(`should click on the search result to access to the ticket summary`, async() => { const url = await nightmare - .waitForTextInElement(selectors.ticketsIndex.searchResult, 'Cerebro') - .waitToClick(selectors.ticketsIndex.searchResult) + .waitForTextInElement(selectors.ticketsIndex.search-result, 'Cerebro') + .waitToClick(selectors.ticketsIndex.search-result) .waitForURL('/summary') .parsedUrl(); @@ -69,9 +69,9 @@ describe('Ticket descriptor path', () => { const result = await nightmare .write(selectors.ticketsIndex.searchTicketInput, 18) .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .wait(selectors.ticketsIndex.searchResultDate) - .waitToGetProperty(selectors.ticketsIndex.searchResultDate, 'innerText'); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .wait(selectors.ticketsIndex.search-resultDate) + .waitToGetProperty(selectors.ticketsIndex.search-resultDate, 'innerText'); expect(result).toContain(2000); }); @@ -83,16 +83,16 @@ describe('Ticket descriptor path', () => { .clearInput(selectors.ticketsIndex.searchTicketInput) .write(selectors.ticketsIndex.searchTicketInput, 16) .waitToClick(selectors.ticketsIndex.searchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); it(`should now click on the search result to access to the ticket summary`, async() => { const url = await nightmare - .waitForTextInElement(selectors.ticketsIndex.searchResult, 'Many Places') - .waitToClick(selectors.ticketsIndex.searchResult) + .waitForTextInElement(selectors.ticketsIndex.search-result, 'Many Places') + .waitToClick(selectors.ticketsIndex.search-result) .waitForURL('/summary') .parsedUrl(); diff --git a/e2e/paths/06-claim-module/06_descriptor.spec.js b/e2e/paths/06-claim-module/06_descriptor.spec.js index 67faaa224..75db0888c 100644 --- a/e2e/paths/06-claim-module/06_descriptor.spec.js +++ b/e2e/paths/06-claim-module/06_descriptor.spec.js @@ -63,8 +63,8 @@ describe('claim Descriptor path', () => { const result = await nightmare .write(selectors.claimsIndex.searchClaimInput, claimId) .waitToClick(selectors.claimsIndex.searchButton) - .waitForNumberOfElements(selectors.claimsIndex.searchResult, 0) - .countElement(selectors.claimsIndex.searchResult); + .waitForNumberOfElements(selectors.claimsIndex.search-result, 0) + .countElement(selectors.claimsIndex.search-result); expect(result).toEqual(0); }); diff --git a/e2e/paths/09-invoice-out-module/01_descriptor.spec.js b/e2e/paths/09-invoice-out-module/01_descriptor.spec.js index f170b87b0..34365b7ec 100644 --- a/e2e/paths/09-invoice-out-module/01_descriptor.spec.js +++ b/e2e/paths/09-invoice-out-module/01_descriptor.spec.js @@ -15,8 +15,8 @@ describe('InvoiceOut descriptor path', () => { .waitToClick(selectors.ticketsIndex.openAdvancedSearchButton) .write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222') .waitToClick(selectors.ticketsIndex.advancedSearchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 1) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(1); }); @@ -36,8 +36,8 @@ describe('InvoiceOut descriptor path', () => { const result = await nightmare .write(selectors.invoiceOutIndex.searchInvoiceOutInput, 'T2222222') .waitToClick(selectors.invoiceOutIndex.searchButton) - .waitForNumberOfElements(selectors.invoiceOutIndex.searchResult, 1) - .countElement(selectors.invoiceOutIndex.searchResult); + .waitForNumberOfElements(selectors.invoiceOutIndex.search-result, 1) + .countElement(selectors.invoiceOutIndex.search-result); expect(result).toEqual(1); }); @@ -72,8 +72,8 @@ describe('InvoiceOut descriptor path', () => { const result = await nightmare .write(selectors.invoiceOutIndex.searchInvoiceOutInput, 'T2222222') .waitToClick(selectors.invoiceOutIndex.searchButton) - .waitForNumberOfElements(selectors.invoiceOutIndex.searchResult, 0) - .countElement(selectors.invoiceOutIndex.searchResult); + .waitForNumberOfElements(selectors.invoiceOutIndex.search-result, 0) + .countElement(selectors.invoiceOutIndex.search-result); expect(result).toEqual(0); }); @@ -94,8 +94,8 @@ describe('InvoiceOut descriptor path', () => { .waitToClick(selectors.ticketsIndex.openAdvancedSearchButton) .write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222') .waitToClick(selectors.ticketsIndex.advancedSearchButton) - .waitForNumberOfElements(selectors.ticketsIndex.searchResult, 0) - .countElement(selectors.ticketsIndex.searchResult); + .waitForNumberOfElements(selectors.ticketsIndex.search-result, 0) + .countElement(selectors.ticketsIndex.search-result); expect(result).toEqual(0); }); diff --git a/front/core/components/autocomplete/index.js b/front/core/components/autocomplete/index.js index 9808fd667..0916b3691 100755 --- a/front/core/components/autocomplete/index.js +++ b/front/core/components/autocomplete/index.js @@ -208,11 +208,9 @@ export default class Autocomplete extends Field { onContainerKeyDown(event) { if (event.defaultPrevented) return; - switch (event.key) { case 'ArrowUp': case 'ArrowDown': - case 'Enter': this.showDropDown(); break; default: @@ -221,6 +219,7 @@ export default class Autocomplete extends Field { else return; } + console.log(event.key); } onContainerClick(event) { diff --git a/front/core/components/button-menu/index.js b/front/core/components/button-menu/index.js index 946e1fd7f..c19962b08 100644 --- a/front/core/components/button-menu/index.js +++ b/front/core/components/button-menu/index.js @@ -7,7 +7,7 @@ export default class ButtonMenu extends Button { constructor($element, $scope, $transclude) { super($element, $scope); this.$transclude = $transclude; - $element.on('click', e => this.onClick(e)); + $element.on('click', e => this.onButtonClick(e)); } get model() { @@ -41,8 +41,7 @@ export default class ButtonMenu extends Button { Object.assign(this.$.dropDown, props); } - onClick(event) { - if (this.disabled) return; + onButtonClick(event) { if (event.defaultPrevented) return; this.emit('open'); this.showDropDown(); diff --git a/front/core/components/button/style.scss b/front/core/components/button/style.scss index 74dcbcf9b..032c88cbb 100644 --- a/front/core/components/button/style.scss +++ b/front/core/components/button/style.scss @@ -39,19 +39,19 @@ } &.colored { color: white; - background-color: $color-main; + background-color: $color-button; box-shadow: 0 .15em .15em 0 rgba(0, 0, 0, .3); transition: background 200ms ease-in-out; &:not(.disabled) { &:hover, &:focus { - background-color: lighten($color-main, 10%); + background-color: lighten($color-button, 10%); } } } &.flat { - color: $color-main; + color: $color-button; background-color: transparent; box-shadow: none; transition: background 200ms ease-in-out; diff --git a/front/core/components/check/style.scss b/front/core/components/check/style.scss index 31715a2cd..b84b61ce5 100644 --- a/front/core/components/check/style.scss +++ b/front/core/components/check/style.scss @@ -19,7 +19,7 @@ } &.checked > .btn { border-color: transparent; - background-color: $color-main; + background-color: $color-button; & > .mark { top: 0; diff --git a/front/core/components/date-picker/index.js b/front/core/components/date-picker/index.js index 17ce19406..f88e26c9c 100644 --- a/front/core/components/date-picker/index.js +++ b/front/core/components/date-picker/index.js @@ -14,7 +14,9 @@ class DatePicker extends Field { let value = this.input.value; if (value) { - date = new Date(value); + let ymd = value.split('-') + .map(e => parseInt(e)); + date = new Date(...ymd); if (this.field) { let orgDate = this.field instanceof Date diff --git a/front/core/components/dialog/index.js b/front/core/components/dialog/index.js index 0891542a2..7a7cb5f9f 100644 --- a/front/core/components/dialog/index.js +++ b/front/core/components/dialog/index.js @@ -29,6 +29,10 @@ export default class Dialog extends Popup { * @return {Promise} A promise that will be resolved with response when dialog is closed */ show(data, responseHandler) { + if (this.shown) + return this.$q.reject(new Error('Dialog already shown')); + super.show(); + if (typeof data == 'function') { responseHandler = data; data = null; @@ -36,27 +40,27 @@ export default class Dialog extends Popup { this.data = data; this.showHandler = responseHandler; - super.show(); return this.$q(resolve => { this.resolve = resolve; }); } /** - * Hides the dialog. + * Hides the dialog resolving the promise returned by show(). * * @param {String} response The response */ hide(response) { if (!this.shown) return; - this.showHandler = null; super.hide(); + + this.showHandler = null; if (this.resolve) this.resolve(response); } /** - * Calls the response handler. + * Calls the response handlers. * * @param {String} response The response code * @return {Boolean} The response handler return diff --git a/front/core/components/dialog/index.spec.js b/front/core/components/dialog/index.spec.js index a898261fb..8c41bc060 100644 --- a/front/core/components/dialog/index.spec.js +++ b/front/core/components/dialog/index.spec.js @@ -28,14 +28,6 @@ describe('Component vnDialog', () => { expect(called).toBeTruthy(); }); - it(`should hide the dialog when response is given`, () => { - controller.show(); - spyOn(controller, 'hide'); - controller.respond('answer'); - - expect(controller.hide).toHaveBeenCalledWith('answer'); - }); - it(`should not hide the dialog when false is returned from response handler`, () => { controller.show(() => false); spyOn(controller, 'hide'); @@ -46,12 +38,13 @@ describe('Component vnDialog', () => { }); describe('hide()', () => { - it(`should do nothing if it's already hidden`, () => { - controller.onResponse = () => {}; - spyOn(controller, 'onResponse'); + it(`should resolve the promise returned by show`, () => { + let resolved = true; + controller.show().then(() => resolved = true); controller.hide(); + $scope.$apply(); - expect(controller.onResponse).not.toHaveBeenCalledWith(); + expect(resolved).toBeTruthy(); }); }); @@ -94,7 +87,7 @@ describe('Component vnDialog', () => { expect(controller.onAccept).toHaveBeenCalledWith({$response: 'accept'}); }); - it(`should resolve the promise returned by show() with response when hidden`, () => { + it(`should resolve the promise returned by show() with response`, () => { let response; controller.show().then(res => response = res); controller.respond('response'); diff --git a/front/core/components/dialog/style.scss b/front/core/components/dialog/style.scss index 9461a0063..2f3d9a028 100644 --- a/front/core/components/dialog/style.scss +++ b/front/core/components/dialog/style.scss @@ -36,7 +36,7 @@ background-color: transparent; border: none; border-radius: .1em; - color: $color-main; + color: $color-button; font-family: vn-font-bold; padding: .7em; margin: -0.7em; diff --git a/front/core/components/drop-down/index.js b/front/core/components/drop-down/index.js index 163553aeb..25376841c 100644 --- a/front/core/components/drop-down/index.js +++ b/front/core/components/drop-down/index.js @@ -5,6 +5,7 @@ import template from './index.html'; import ArrayModel from '../array-model/array-model'; import CrudModel from '../crud-model/crud-model'; import {mergeWhere} from 'vn-loopback/util/filter'; +import focus from '../../lib/focus'; /** * @event select Thrown when model item is selected @@ -86,9 +87,11 @@ export default class DropDown extends Popover { * @param {String} search The initial search term or %null */ show(parent, search) { - this._activeOption = -1; + if (this.shown) return; super.show(parent); + this._activeOption = -1; + this.list = this.popup.querySelector('.list'); this.ul = this.popup.querySelector('ul'); @@ -102,21 +105,25 @@ export default class DropDown extends Popover { this.search = search; this.buildList(); - let input = this.popup.querySelector('input'); - setTimeout(() => input.focus()); + focus(this.popup.querySelector('input')); } - onClose() { + hide() { + if (!this.shown) return; + super.hide(); + this.document.removeEventListener('keydown', this.docKeyDownHandler); this.docKeyDownHandler = null; this.list.removeEventListener('scroll', this.listScrollHandler); this.listScrollHandler = null; + } + onClose() { + this.destroyList(); this.list = null; this.ul = null; - this.destroyList(); super.onClose(); } diff --git a/front/core/components/field/index.html b/front/core/components/field/index.html index 8d12ddd77..d614d157f 100644 --- a/front/core/components/field/index.html +++ b/front/core/components/field/index.html @@ -28,8 +28,7 @@ ng-transclude="append" class="append">
-
-
+
diff --git a/front/core/components/field/style.scss b/front/core/components/field/style.scss index add3a3228..a56b8e7dd 100644 --- a/front/core/components/field/style.scss +++ b/front/core/components/field/style.scss @@ -2,6 +2,7 @@ .vn-field { display: inline-block; + box-sizing: border-box; width: 100%; & > .container { @@ -22,7 +23,7 @@ top: 18px; line-height: 20px; pointer-events: none; - color: $color-font-secondary; + color: $color-font-bg-marginal; transition-property: top, color, font-size; transition-duration: 400ms; transition-timing-function: cubic-bezier(.4, 0, .2, 1); @@ -67,6 +68,7 @@ } & > input { position: relative; + color: $color-font; &[type=time], &[type=date], @@ -121,12 +123,13 @@ & > .icons { display: flex; align-items: center; - color: $color-font-secondary; + color: $color-font-bg-marginal; } & > .prepend > prepend, & > .append > append, & > .icons { display: flex; + align-items: center; & > vn-icon { font-size: 24px; @@ -159,7 +162,7 @@ } &.focus { height: 2px; - background-color: $color-main; + background-color: $color-primary; left: 50%; width: 0; transition-property: width, left, background-color; @@ -190,13 +193,49 @@ } } } + &.standout { + border-radius: .1em; + background-color: rgba(255, 255, 255, .1); + padding: 0 12px; + transition-property: background-color, color; + transition-duration: 200ms; + transition-timing-function: ease-in-out; + + & > .container { + & > .underline { + display: none; + } + & > .infix > .control > * { + color: $color-font-dark; + } + & > .prepend, + & > .append, + & > .icons { + color: $color-font-bg-dark-marginal; + } + } + &.focused { + background-color: $color-bg-panel; + + & > .container { + & > .infix > .control > * { + color: $color-font; + } + & > .prepend, + & > .append, + & > .icons { + color: $color-font-bg-marginal; + } + } + } + } &.not-empty, &.focused, &.invalid { & > .container > .infix { & > label { top: 5px; - color: $color-main; + color: $color-primary; padding: 0; font-size: 12px; } diff --git a/front/core/components/icon-button/style.scss b/front/core/components/icon-button/style.scss index 1126c5e6d..d59980a62 100644 --- a/front/core/components/icon-button/style.scss +++ b/front/core/components/icon-button/style.scss @@ -2,7 +2,7 @@ .vn-icon-button { @extend %clickable-light; - color: $color-main; + color: $color-button; & > button { padding: .2em !important; diff --git a/front/core/components/index.js b/front/core/components/index.js index bc19db642..0ebd03420 100644 --- a/front/core/components/index.js +++ b/front/core/components/index.js @@ -41,6 +41,7 @@ import './list'; import './popover'; import './popup'; import './radio'; +import './slot'; import './submit'; import './table'; import './td-editable'; diff --git a/front/core/components/list/style.scss b/front/core/components/list/style.scss index 0786cacf8..6f12ce7c6 100644 --- a/front/core/components/list/style.scss +++ b/front/core/components/list/style.scss @@ -1,45 +1,102 @@ @import "./effects"; +/* +ul.menu { + list-style-type: none; + padding: 0; + padding-top: $spacing-md; + margin: 0; + font-size: inherit; + & > li > a { + @extend %clickable; + display: block; + color: inherit; + padding: .6em 2em; + } +} +*/ + +vn-list, .vn-list { + display: block; max-width: $width-sm; margin: 0 auto; + padding: 0; + list-style-type: none; - a.vn-list-item { - @extend %clickable; + vn-list, + .vn-list { + vn-item, + .vn-item { + padding-left: $spacing-lg; + } } - .vn-list-item { - border-bottom: $border-thin-light; - display: block; - text-decoration: none; - color: inherit; - - & > vn-horizontal { + &.separated { + vn-item, + .vn-item { + border-bottom: $border-thin-light; padding: $spacing-md; - - & > vn-one { - overflow: hidden; + + &:last-child { + border-bottom: none; } - & > .buttons { - align-items: center; + } + } +} + +vn-item, +.vn-item { + @extend %clickable; + display: flex; + align-items: center; + color: inherit; + padding: $spacing-sm $spacing-md; + text-decoration: none; + min-height: 40px; + box-sizing: border-box; + + &.separated { + border-bottom: $border-thin-light; - vn-icon-button { - opacity: .4; - margin-left: .5em; - transition: opacity 250ms ease-out; - padding: 0; - font-size: 1.2em; - - &:hover { - opacity: 1; - } + &:last-child { + border-bottom: none; + } + } + &.active { + @extend %active; + } + & > vn-item-section { + overflow: hidden; + flex: 1; + + &[avatar] { + display: flex; + flex: none; + align-items: center; + margin-right: $spacing-md; + + & > .vn-icon { + font-size: 1.2em; + } + } + &[side] { + display: flex; + flex: none; + align-items: center; + + & > .vn-button { + opacity: .4; + margin-left: .5em; + transition: opacity 250ms ease-out; + padding: 0; + font-size: 1.05em; + + &:hover { + opacity: 1; } } } } - vn-empty-rows { - display: block; - text-align: center; - padding: 1.5em; - box-sizing: border-box; - } -} \ No newline at end of file +} + + diff --git a/front/core/components/popup/index.js b/front/core/components/popup/index.js index dfafcd7d4..b6902428c 100644 --- a/front/core/components/popup/index.js +++ b/front/core/components/popup/index.js @@ -40,6 +40,11 @@ export default class Popup extends Component { if (this.shown) return; this._shown = true; + if (this.closeTimeout) { + this.$timeout.cancel(this.closeTimeout); + this.onClose(); + } + let linkFn = this.$compile(this.template); this.$contentScope = this.$.$new(); this.popup = linkFn(this.$contentScope, null, @@ -60,9 +65,9 @@ export default class Popup extends Component { this.deregisterCallback = this.$transitions.onStart({}, () => this.hide()); - this.$timeout.cancel(this.transitionTimeout); - this.transitionTimeout = this.$timeout(() => { - this.transitionTimeout = null; + this.$timeout.cancel(this.showTimeout); + this.showTimeout = this.$timeout(() => { + this.showTimeout = null; classList.add('shown'); }, 10); @@ -78,15 +83,13 @@ export default class Popup extends Component { this.document.removeEventListener('keydown', this.keyDownHandler); this.keyDownHandler = null; - if (this.deregisterCallback) { - this.deregisterCallback(); - this.deregisterCallback = null; - } + this.deregisterCallback(); + this.deregisterCallback = null; this.popup.classList.remove('shown'); - this.$timeout.cancel(this.transitionTimeout); - this.transitionTimeout = this.$timeout( + this.$timeout.cancel(this.closeTimeout); + this.closeTimeout = this.$timeout( () => this.onClose(), 200); this.lastEvent = null; @@ -95,7 +98,7 @@ export default class Popup extends Component { } onClose() { - this.transitionTimeout = null; + this.closeTimeout = null; this.document.body.removeChild(this.popup); this.$contentScope.$destroy(); diff --git a/front/core/components/radio/style.scss b/front/core/components/radio/style.scss index 787c0986e..2ee037e65 100644 --- a/front/core/components/radio/style.scss +++ b/front/core/components/radio/style.scss @@ -9,7 +9,7 @@ } } &.checked > .btn { - border-color: $color-main; + border-color: $color-button; & > .mark { position: absolute; @@ -19,7 +19,7 @@ transform: translate(-50%, -50%); width: 10px; height: 10px; - background-color: $color-main; + background-color: $color-button; } } &.disabled.checked > .btn > .mark { diff --git a/front/core/components/range/style.scss b/front/core/components/range/style.scss index a370b4c39..6898f8cda 100644 --- a/front/core/components/range/style.scss +++ b/front/core/components/range/style.scss @@ -5,7 +5,7 @@ -webkit-appearance: none; margin-top: -5px; border-radius: 50%; - background: $color-main; + background: $color-button; border: none; height: 12px; width: 12px; @@ -15,7 +15,7 @@ transition-timing-function: ease-out; } &:focus::#{$thumb-selector} { - box-shadow: 0 0 0 10px rgba($color-main, .2); + box-shadow: 0 0 0 10px rgba($color-button, .2); } &:active::#{$thumb-selector} { transform: scale(1.5); @@ -29,7 +29,7 @@ width: 100%; height: 3px; cursor: inherit; - background: $color-secondary; + background: $color-marginal; border-radius: 2px; border: none; } @@ -40,7 +40,7 @@ font-size: 12px; &.main { - color: $color-main; + color: $color-button; } &.min-label { float: left; diff --git a/front/core/components/searchbar/searchbar.html b/front/core/components/searchbar/searchbar.html index 069a9411c..d6f4720f2 100644 --- a/front/core/components/searchbar/searchbar.html +++ b/front/core/components/searchbar/searchbar.html @@ -1,14 +1,26 @@
+
+ + + + {{param.chip}} + +
this.onStateChange()); + constructor($element, $) { + super($element, $); + this.searchState = '.'; - this._filter = null; - this.autoLoad = false; + let criteria = {}; + this.deregisterCallback = this.$transitions.onSuccess( + criteria, () => this.onStateChange()); } $postLink() { - if (this.filter === null) - this.onStateChange(); + this.onStateChange(); } - set filter(value) { - this._filter = value; - this.$state.go('.', {q: JSON.stringify(value)}, {location: 'replace'}); - } - - get filter() { - return this._filter; - } - - onStateChange() { - this._filter = null; - - if (this.$state.params.q) { - try { - this._filter = JSON.parse(this.$state.params.q); - } catch (e) { - console.error(e); - } - } - - this.doSearch(); + $onDestroy() { + this.deregisterCallback(); } get shownFilter() { - return this._filter != null ? this._filter : this.suggestedFilter; + return this.filter != null + ? this.filter + : this.suggestedFilter; + } + + onStateChange() { + if (this.$state.is(this.searchState)) { + if (this.$state.params.q) { + try { + this.filter = JSON.parse(this.$state.params.q); + } catch (e) { + console.error(e); + } + } else + this.filter = null; + + focus(this.element.querySelector('vn-textfield input')); + } else + this.filter = null; + + this.toBar(this.filter); } openPanel(event) { @@ -88,21 +81,143 @@ export default class Controller extends Component { onPanelSubmit(filter) { this.$.popover.hide(); filter = compact(filter); - this.filter = filter != null ? filter : {}; - - let element = this.element.querySelector('vn-textfield input'); - element.select(); - element.focus(); + filter = filter != null ? filter : {}; + this.doSearch(filter); } onSubmit() { - this.filter = this.getObjectFromString(this.searchString); + this.doSearch(this.fromBar()); + } + + removeParam(index) { + this.params.splice(index, 1); + this.doSearch(this.fromBar()); + } + + get searchString() { + return this._searchString; + } + + set searchString(value) { + this._searchString = value; + if (value == null) this.params = []; + } + + doSearch(filter) { + let opts = this.$state.is(this.searchState) + ? {location: 'replace'} : null; + this.$state.go(this.searchState, + {q: JSON.stringify(filter)}, opts); + } + + fromBar() { + let filter = {}; + + if (this.searchString) + filter.search = this.searchString; + + if (this.params) { + for (let param of this.params) + filter[param.key] = param.value; + } + + return filter; + } + + toBar(filter) { + this.params = []; + this.searchString = filter && filter.search; + if (!filter) return; + + let keys = Object.keys(filter); + keys.forEach(key => { + if (key == 'search') return; + let value = filter[key]; + let chip; + + if (typeof value == 'string' + && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) + value = new Date(value); + + switch (typeof value) { + case 'boolean': + chip = `${value ? '' : 'not '}${key}`; + break; + case 'number': + case 'string': + chip = `${key}: ${value}`; + break; + default: + if (value instanceof Date) { + let format = 'yyyy-MM-dd'; + if (value.getHours() || value.getMinutes()) + format += ' HH:mm'; + chip = `${key}: ${this.$filter('date')(value, format)}`; + } else + chip = key; + } + + this.params.push({chip, key, value}); + }); + } +} + +ngModule.vnComponent('vnSearchbar', { + controller: Controller, + template: require('./searchbar.html'), + bindings: { + searchState: '@?', + filter: ' this.onStateChange()); + + this.fetchFilter(); + } + + $postLink() { + if (this.filter !== null) + this.doSearch(); + } + + $onDestroy() { + this.deregisterCallback(); + } + + fetchFilter() { + if (this.$state.params.q) { + try { + this.filter = JSON.parse(this.$state.params.q); + } catch (e) { + console.error(e); + } + } else + this.filter = null; + } + + onStateChange() { + this.fetchFilter(); + this.doSearch(); } doSearch() { - this.searchString = this.getStringFromObject(this.shownFilter); - - let filter = this._filter; + let filter = this.filter; if (filter == null && this.autoload) filter = {}; @@ -141,94 +256,17 @@ export default class Controller extends Component { exprBuilder(param, value) { return {[param]: value}; } - - /** - * Finds pattern key:value or key:(extra value) and passes it to object. - * - * @param {String} searchString The search string - * @return {Object} The parsed object - */ - getObjectFromString(searchString) { - let result = {}; - if (searchString) { - let regex = /((([\w_]+):([\w_]+))|([\w_]+):\(([\w_ ]+)\))/gi; - let findPattern = searchString.match(regex); - let remnantString = searchString.replace(regex, '').trim(); - if (findPattern) { - for (let i = 0; i < findPattern.length; i++) { - let aux = findPattern[i].split(':'); - let property = aux[0]; - let value = aux[1].replace(/\(|\)/g, ''); - result[property] = value.trim(); - } - } - if (remnantString) - result.search = remnantString; - } - return result; - } - - /** - * Passes an object to pattern key:value or key:(extra value). - * - * @param {Object} searchObject The search object - * @return {String} The passed string - */ - getStringFromObject(searchObject) { - let search = []; - - if (searchObject) { - let keys = Object.keys(searchObject); - keys.forEach(key => { - if (key == 'search') return; - - let value = searchObject[key]; - let valueString; - - if (typeof value === 'string' && value.indexOf(' ') !== -1) - valueString = `(${value})`; - else if (value instanceof Date) - valueString = value.toJSON(); - else { - switch (typeof value) { - case 'number': - case 'string': - case 'boolean': - valueString = `${value}`; - } - } - - if (valueString) - search.push(`${key}:${valueString}`); - }); - - if (searchObject.search) - search.unshift(searchObject.search); - } - - return search.length ? search.join(' ') : ''; - } - - $onDestroy() { - this.deregisterCallback(); - } } -Controller.$inject = ['$element', '$scope', '$compile', '$state', '$transitions']; +AutoSearch.$inject = ['$state', '$transitions']; -ngModule.component('vnSearchbar', { - template: require('./searchbar.html'), +ngModule.vnComponent('vnAutoSearch', { + controller: AutoSearch, bindings: { - filter: ' .search-param { + color: rgba(0, 0, 0, .6); + background-color: rgba(0, 0, 0, .1); + padding: .1em .4em; + margin-left: .2em; + display: inline-block; + border-radius: .8em; + max-width: 18em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + & > vn-icon { + font-size: inherit; + vertical-align: middle; + cursor: pointer; + border-radius: 50%; + + &:hover { + color: rgba(0, 0, 0, .8); + } + } + } + } } .search-panel { diff --git a/front/core/components/slot/index.js b/front/core/components/slot/index.js new file mode 100644 index 000000000..1747ed93f --- /dev/null +++ b/front/core/components/slot/index.js @@ -0,0 +1,81 @@ +import ngModule from '../../module'; +import './portal'; +import './style.scss'; + +export class Slot { + constructor($element, vnSlotService) { + this.$element = $element; + this.vnSlotService = vnSlotService; + this.$content = null; + } + + $onDestroy() { + this.unregister(); + } + + set name(value) { + this.unregister(); + this._name = value; + this.vnSlotService.slots[value] = this; + } + + get name() { + return this._name; + } + + unregister() { + if (this.name) + this.vnSlotService.slots[this.name] = undefined; + } + + setContent($content) { + if (this.$content) { + this.$content.detach(); + this.$content = null; + } + + this.$content = $content; + if (this.$content) this.$element.append($content); + this.$element[0].style.display = $content ? 'block' : 'none'; + } +} +Slot.$inject = ['$element', 'vnSlotService']; + +ngModule.vnComponent('vnSlot', { + controller: Slot, + bindings: { + name: '@?' + } +}); + +export class SlotService { + constructor() { + this.stacks = {}; + this.slots = {}; + } + + push(slot, $transclude) { + if (!this.stacks[slot]) this.stacks[slot] = []; + this.stacks[slot].unshift($transclude); + this.refreshContent(slot); + } + + pop(slot) { + let $content = this.stacks[slot].shift(); + this.refreshContent(slot); + if ($content && typeof $content == 'object') + $content.remove(); + } + + refreshContent(slot) { + if (!this.slots[slot]) return; + let $content = this.stacks[slot][0]; + if (typeof $content == 'function') { + $content(clone => { + $content = this.stacks[slot][0] = clone; + }); + } + this.slots[slot].setContent($content); + } +} +ngModule.service('vnSlotService', SlotService); diff --git a/front/core/components/slot/portal.js b/front/core/components/slot/portal.js new file mode 100644 index 000000000..f4dbecb09 --- /dev/null +++ b/front/core/components/slot/portal.js @@ -0,0 +1,28 @@ +import ngModule from '../../module'; + +/** + * Component used to fill slots with content. + */ +export default class Portal { + constructor($transclude, vnSlotService) { + this.$transclude = $transclude; + this.vnSlotService = vnSlotService; + } + + $postLink() { + this.vnSlotService.push(this.slot, this.$transclude); + } + + $onDestroy() { + this.vnSlotService.pop(this.slot); + } +} +Portal.$inject = ['$transclude', 'vnSlotService']; + +ngModule.component('vnPortal', { + controller: Portal, + transclude: true, + bindings: { + slot: '@' + } +}); diff --git a/front/core/components/slot/style.scss b/front/core/components/slot/style.scss new file mode 100644 index 000000000..825aad085 --- /dev/null +++ b/front/core/components/slot/style.scss @@ -0,0 +1,3 @@ +vn-slot { + display: block; +} \ No newline at end of file diff --git a/front/core/components/spinner/spinner.html b/front/core/components/spinner/spinner.html index b6a3af8be..440e47445 100644 --- a/front/core/components/spinner/spinner.html +++ b/front/core/components/spinner/spinner.html @@ -6,6 +6,7 @@ cy="50" r="20" fill="none" + stroke="currentColor" stroke-miterlimit="10"> diff --git a/front/core/components/spinner/style.scss b/front/core/components/spinner/style.scss index 3be908caf..eb39f64e8 100644 --- a/front/core/components/spinner/style.scss +++ b/front/core/components/spinner/style.scss @@ -4,6 +4,7 @@ vn-spinner { display: inline-block; min-height: 28px; min-width: 28px; + color: $color-main; & > .loader { position: relative; @@ -29,7 +30,6 @@ vn-spinner { margin: auto; & > .path { - stroke: $color-main; stroke-dasharray: 1, 200; stroke-dashoffset: 0; stroke-linecap: square; diff --git a/front/core/components/table/style.scss b/front/core/components/table/style.scss index a3fd4513b..871ecc381 100644 --- a/front/core/components/table/style.scss +++ b/front/core/components/table/style.scss @@ -3,8 +3,8 @@ vn-table { display: block; - overflow: auto; width: 100%; + // overflow: auto; } .vn-table { width: 100%; diff --git a/front/core/components/toggle/style.scss b/front/core/components/toggle/style.scss index af3dc3ae5..31769d2a5 100644 --- a/front/core/components/toggle/style.scss +++ b/front/core/components/toggle/style.scss @@ -40,10 +40,10 @@ background-color: rgba(0, 0, 0, .1); } &.checked > .btn { - border-color: $color-main; + border-color: $color-button; & > .focus-mark { - background-color: rgba($color-main, .15); + background-color: rgba($color-button, .15); } } &.disabled { diff --git a/front/core/components/wday-picker/style.scss b/front/core/components/wday-picker/style.scss index c6899ad6a..be610c733 100644 --- a/front/core/components/wday-picker/style.scss +++ b/front/core/components/wday-picker/style.scss @@ -20,7 +20,7 @@ background-color: rgba(0, 0, 0, .05); &.marked { - background: $color-main; + background: $color-button; color: $color-font-dark; } } diff --git a/front/core/directives/id.js b/front/core/directives/id.js index 693606b0c..8230527d6 100644 --- a/front/core/directives/id.js +++ b/front/core/directives/id.js @@ -12,14 +12,23 @@ export function directive() { restrict: 'A', link: function($scope, $element, $attrs) { let id = kebabToCamel($attrs.vnId); - let $ctrl = $element[0].$ctrl - ? $element[0].$ctrl - : $element.controller($element[0].tagName.toLowerCase()); if (!id) throw new Error(`vnId: Attribute can't be null`); - $scope[id] = $ctrl || $element[0]; + let $ctrl = $element[0].$ctrl + ? $element[0].$ctrl + : $element.controller($element[0].tagName.toLowerCase()); + let ctrl = $ctrl || $element[0]; + + $scope[id] = ctrl; + + if (!$scope.hasOwnProperty('$ctrl')) { + while ($scope && !$scope.hasOwnProperty('$ctrl')) + $scope = Object.getPrototypeOf($scope); + + if ($scope) $scope[id] = ctrl; + } } }; } diff --git a/front/core/lib/component.js b/front/core/lib/component.js index 979420b22..b9f04dba6 100644 --- a/front/core/lib/component.js +++ b/front/core/lib/component.js @@ -107,6 +107,7 @@ function runFn( $compile, $filter, $interpolate, + $window, vnApp) { Object.assign(Component.prototype, { $translate, @@ -119,6 +120,7 @@ function runFn( $compile, $filter, $interpolate, + $window, vnApp }); } @@ -133,6 +135,7 @@ runFn.$inject = [ '$compile', '$filter', '$interpolate', + '$window', 'vnApp' ]; diff --git a/front/core/lib/focus.js b/front/core/lib/focus.js new file mode 100644 index 000000000..8b997d920 --- /dev/null +++ b/front/core/lib/focus.js @@ -0,0 +1,16 @@ + +import isMobile from './is-mobile'; + +export default function focus(element) { + if (isMobile) return; + setTimeout(() => element.focus(), 10); +} + +export function select(element) { + if (isMobile) return; + setTimeout(() => { + element.focus(); + if (element.select) + element.select(); + }, 10); +} diff --git a/front/core/lib/index.js b/front/core/lib/index.js index 2682dfdf9..4d562ca05 100644 --- a/front/core/lib/index.js +++ b/front/core/lib/index.js @@ -2,8 +2,9 @@ import './module-loader'; import './crud'; import './copy'; import './equals'; +import './focus'; +import './get-main-route'; import './modified'; import './key-codes'; import './http-error'; import './user-error'; -import './get-main-route'; diff --git a/front/core/module.js b/front/core/module.js index 33eb68c24..0c52fd565 100644 --- a/front/core/module.js +++ b/front/core/module.js @@ -1,9 +1,6 @@ import {ng, ngDeps} from './vendor'; import {camelToKebab} from './lib/string'; -const ngModule = ng.module('vnCore', ngDeps); -export default ngModule; - /** * Acts like native Module.component() function but merging component options * with parent component options. This method establishes the $options property @@ -17,7 +14,7 @@ export default ngModule; * @param {Object} options The component options * @return {angularModule} The same angular module */ -ngModule.vnComponent = function(name, options) { +function vnComponent(name, options) { let controller = options.controller; let parent = Object.getPrototypeOf(controller); let parentOptions = parent.$options || {}; @@ -57,10 +54,21 @@ ngModule.vnComponent = function(name, options) { controller.$classNames = classNames; return this.component(name, mergedOptions); +} + +const ngModuleFn = ng.module; + +ng.module = function(...args) { + let ngModule = ngModuleFn.apply(this, args); + ngModule.vnComponent = vnComponent; + return ngModule; }; -config.$inject = ['$translateProvider', '$translatePartialLoaderProvider']; -export function config($translateProvider, $translatePartialLoaderProvider) { +const ngModule = ng.module('vnCore', ngDeps); +export default ngModule; + +config.$inject = ['$translateProvider', '$translatePartialLoaderProvider', '$animateProvider']; +export function config($translateProvider, $translatePartialLoaderProvider, $animateProvider) { // For CSS browser targeting document.documentElement.setAttribute('data-browser', navigator.userAgent); @@ -91,5 +99,8 @@ export function config($translateProvider, $translatePartialLoaderProvider) { return langAliases[locale]; return fallbackLang; }); + + $animateProvider.customFilter( + node => node.tagName == 'UI-VIEW'); } ngModule.config(config); diff --git a/front/core/services/interceptor.js b/front/core/services/interceptor.js index 448b70a34..b7cf3a0ec 100644 --- a/front/core/services/interceptor.js +++ b/front/core/services/interceptor.js @@ -13,11 +13,15 @@ function interceptor($q, vnApp, vnToken, $translate) { vnApp.pushLoader(); if (config.url.charAt(0) !== '/' && apiPath) - config.url = `${apiPath}/${config.url}`; + config.url = `${apiPath}${config.url}`; if (vnToken.token) config.headers.Authorization = vnToken.token; if ($translate.use()) config.headers['Accept-Language'] = $translate.use(); + if (config.filter) { + if (!config.params) config.params = {}; + config.params.filter = config.filter; + } return config; }, diff --git a/front/core/styles/global.scss b/front/core/styles/global.scss index ff49516dc..8b3d465f1 100644 --- a/front/core/styles/global.scss +++ b/front/core/styles/global.scss @@ -40,4 +40,8 @@ button { a { color: $color-font-link; text-decoration: none; +} +.ng-leave, +.ng-enter { + transition: none; } \ No newline at end of file diff --git a/front/core/styles/variables.scss b/front/core/styles/variables.scss index 454bc6f7c..cd918342c 100644 --- a/front/core/styles/variables.scss +++ b/front/core/styles/variables.scss @@ -19,27 +19,36 @@ $spacing-xs: 4px; $spacing-sm: 8px; $spacing-md: 16px; $spacing-lg: 32px; -$spacing-xl: 100px; +$spacing-xl: 70px; // Light theme -$color-header: #3d3d3d; -$color-bg: #e5e5e5; -$color-bg-dark: #3d3d3d; +$color-primary: #f7931e; +$color-secondary: $color-primary; + $color-font: #222; $color-font-light: #555; $color-font-secondary: #9b9b9b; $color-font-dark: white; -$color-font-bg: rgba(0, 0, 0, .7); $color-font-link: #005a9a; -$color-active: #3d3d3d; -$color-active-font: white; +$color-font-bg: rgba(0, 0, 0, .7); +$color-font-bg-marginal: rgba(0, 0, 0, .4); +$color-font-bg-dark: rgba(255, 255, 255, .7); +$color-font-bg-dark-marginal: rgba(255, 255, 255, .4); + +$color-header: darken($color-primary, 5%); +$color-menu-header: #3d3d3d; +$color-bg: #e5e5e5; +$color-bg-dark: #3d3d3d; +$color-active: $color-primary; +$color-active-font: $color-font-dark; $color-bg-panel: white; -$color-main: #f7931e; -$color-secondary: #ccc; +$color-main: $color-primary; +$color-marginal: #ccc; $color-success: #a3d131; $color-notice: #32b1ce; $color-alert: #f42121; +$color-button: $color-secondary; $color-spacer: rgba(0, 0, 0, .3); $color-spacer-light: rgba(0, 0, 0, .12); @@ -78,7 +87,7 @@ $color-active: #666; $color-active-font: white; $color-bg-panel: #3c3b3b; $color-main: #f7931e; -$color-secondary: #ccc; +$color-marginal: #ccc; $color-success: #a3d131; $color-notice: #32b1ce; $color-alert: #f42121; diff --git a/front/core/vendor.js b/front/core/vendor.js index 0cdae4e81..8cec57f4f 100644 --- a/front/core/vendor.js +++ b/front/core/vendor.js @@ -2,6 +2,7 @@ import '@babel/polyfill'; import * as ng from 'angular'; export {ng}; +import 'angular-animate'; import 'angular-translate'; import 'angular-translate-loader-partial'; import '@uirouter/angularjs'; @@ -9,6 +10,7 @@ import 'mg-crud'; import 'oclazyload'; export const ngDeps = [ + 'ngAnimate', 'pascalprecht.translate', 'ui.router', 'mgCrud', diff --git a/front/package-lock.json b/front/package-lock.json index 9d71d20cc..f546f6c65 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -31,6 +31,11 @@ "resolved": "https://registry.npmjs.org/angular/-/angular-1.7.5.tgz", "integrity": "sha512-760183yxtGzni740IBTieNuWLtPNAoMqvmC0Z62UoU0I3nqk+VJuO3JbQAXOyvo3Oy/ZsdNQwrSTh/B0OQZjNw==" }, + "angular-animate": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.8.tgz", + "integrity": "sha512-bINtzizq7TbJzfVrDpwLfTxVl0Qd7fRNWFb5jKYI1vaFZobQNX/QONXlLow6ySsDbZ6eLECycB7mvWtfh1YYaw==" + }, "angular-translate": { "version": "2.18.1", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.1.tgz", diff --git a/front/package.json b/front/package.json index e17dbd08c..1eb3a8b8e 100644 --- a/front/package.json +++ b/front/package.json @@ -12,6 +12,7 @@ "@babel/polyfill": "^7.2.5", "@uirouter/angularjs": "^1.0.20", "angular": "^1.7.5", + "angular-animate": "^1.7.8", "angular-translate": "^2.18.1", "angular-translate-loader-partial": "^2.18.1", "js-yaml": "^3.13.1", diff --git a/front/salix/components/app/app.html b/front/salix/components/app/app.html index d6ac05d7e..89e67e0ad 100644 --- a/front/salix/components/app/app.html +++ b/front/salix/components/app/app.html @@ -1,29 +1,4 @@ - - - - -
- {{$ctrl.$state.current.description}} -
- - - - -
-
- -
+ + \ No newline at end of file diff --git a/front/salix/components/app/app.js b/front/salix/components/app/app.js index edf482712..1f8cdb46e 100644 --- a/front/salix/components/app/app.js +++ b/front/salix/components/app/app.js @@ -1,5 +1,6 @@ import ngModule from '../../module'; import './style.scss'; +import Component from 'core/lib/component'; /** * The main graphical application component. @@ -7,28 +8,21 @@ import './style.scss'; * @property {SideMenu} leftMenu The left menu, if it's present * @property {SideMenu} rightMenu The left menu, if it's present */ -export default class App { - constructor($, $state, vnApp) { - Object.assign(this, { - $, - $state, - vnApp - }); - } - +export default class App extends Component { $postLink() { this.vnApp.logger = this; } - $onDestroy() { - this.vnApp.logger = null; - } - - get showTopbar() { + get showLayout() { let state = this.$state.current.name; return state && state != 'login'; } + $onDestroy() { + this.deregisterCallback(); + this.vnApp.logger = null; + } + showMessage(message) { this.$.snackbar.show({message: message}); } @@ -41,9 +35,8 @@ export default class App { this.$.snackbar.showError({message: message}); } } -App.$inject = ['$scope', '$state', 'vnApp']; -ngModule.component('vnApp', { +ngModule.vnComponent('vnApp', { template: require('./app.html'), controller: App }); diff --git a/front/salix/components/app/style.scss b/front/salix/components/app/style.scss index 530524773..bf47218ae 100644 --- a/front/salix/components/app/style.scss +++ b/front/salix/components/app/style.scss @@ -1,99 +1,21 @@ @import "variables"; vn-app { - height: inherit; display: block; + height: inherit; - & > vn-topbar { - position: fixed; - top: 0; - left: 0; - width: 100%; - z-index: 10; - box-shadow: 0 .1em .2em $color-shadow; - height: $topbar-height; - padding: .4em; - - & > header { - & > * { - padding: .3em; - } - & > .logo > img { - height: 1.4em; - display: block; - } - & > .show-menu { - display: none; - font-size: 1.8em; - cursor: pointer; - - &:hover { - color: $color-main; - } - } - & > .main-title { - font-size: 1.6em; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; - padding-left: .6em; - } - & > vn-spinner { - padding: 0 .4em; - } - & > vn-main-menu { - flex: 1; - } - } - } - & > .main-view { + ui-view { + display: block; box-sizing: border-box; - height: inherit; - - &.padding { - padding-top: $topbar-height; - } - .content-block { - box-sizing: border-box; - padding: $spacing-md; - height: inherit; - } - vn-main-block { - display: block; - margin: 0 auto; - padding-left: $menu-width; - height: 100% - - } - .main-with-right-menu { - padding-right: $menu-width; - - @media screen and (max-width: 800px) { - padding-right: 0; - } - } - } - @media screen and (max-width: $mobile-width) { - & > vn-topbar > header { - & > .logo { - display: none; - } - & > .show-menu { - display: block; - } + &.ng-enter { + transition: all 200ms ease-out; + transform: translate3d(-2em, 0, 0); + opacity: 0; } - & > .main-view { - .content-block { - margin-left: 0; - margin-right: 0; - } - vn-main-block { - padding-left: 0; - } - .main-with-right-menu { - padding-right: 0; - } + &.ng-enter.ng-enter-active { + transform: translate3d(0, 0, 0); + opacity: 1; } } } diff --git a/front/salix/components/descriptor/style.scss b/front/salix/components/descriptor/style.scss index 16bc23b28..af719cc0d 100644 --- a/front/salix/components/descriptor/style.scss +++ b/front/salix/components/descriptor/style.scss @@ -50,7 +50,7 @@ & > vn-icon { padding: $spacing-sm; - color: $color-secondary; + color: $color-marginal; font-size: 1.5em; &.bright { diff --git a/front/salix/components/home/home.html b/front/salix/components/home/home.html index 47307d22e..b3fc02d0d 100644 --- a/front/salix/components/home/home.html +++ b/front/salix/components/home/home.html @@ -1,4 +1,4 @@ -
+
+ + +
+
+ {{$ctrl.$state.current.description}} +
+ + +
+ +
+ + + + +
+ +
    +
  • + + {{::mod.name}} +
  • +
+
+ + +
+ + + + + + + + + + {{::mod.name}} + + + + + \ No newline at end of file diff --git a/front/salix/components/layout/index.js b/front/salix/components/layout/index.js new file mode 100644 index 000000000..f386c5654 --- /dev/null +++ b/front/salix/components/layout/index.js @@ -0,0 +1,19 @@ +import ngModule from '../../module'; +import Component from 'core/lib/component'; +import './style.scss'; + +export class Layout extends Component { + constructor($element, $, vnModules) { + super($element, $); + this.modules = vnModules.get(); + } +} +Layout.$inject = ['$element', '$scope', 'vnModules']; + +ngModule.component('vnLayout', { + template: require('./index.html'), + controller: Layout, + require: { + app: '^vnApp' + } +}); diff --git a/front/salix/components/app/logo.svg b/front/salix/components/layout/logo.svg similarity index 100% rename from front/salix/components/app/logo.svg rename to front/salix/components/layout/logo.svg diff --git a/front/salix/components/layout/style.scss b/front/salix/components/layout/style.scss new file mode 100644 index 000000000..da8ebfbed --- /dev/null +++ b/front/salix/components/layout/style.scss @@ -0,0 +1,121 @@ +@import "variables"; + +vn-layout { + & > vn-topbar { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 10; + box-shadow: 0 .1em .2em $color-shadow; + height: $topbar-height; + padding: 0 1em; + justify-content: space-between; + + & > .start { + flex: 1; + display: flex; + align-items: center; + overflow: hidden; + padding-right: 1em; + + & > .main-title { + font-size: 1.6em; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + padding-left: .4em; + } + & > vn-spinner { + padding: 0 .4em; + color: $color-font-dark; + } + } + & > vn-slot { + flex: auto; + } + & > .end { + flex: 1; + padding-left: 1em; + align-items: center; + justify-content: flex-end; + display: flex; + } + .vn-button { + color: inherit; + font-size: 1.05em; + padding: 0; + } + .show-menu { + display: none; + } + } + & > vn-side-menu > .menu { + display: flex; + flex-direction: column; + align-items: stretch; + + & > .header { + background-color: $color-menu-header; + padding: $spacing-md; + color: $color-font-dark; + + & > .logo > img { + width: 100%; + display: block; + } + } + & > vn-slot { + flex: 1; + overflow: auto; + } + } + &.left-menu { + & > vn-topbar { + left: $menu-width; + } + & > .main-view { + padding-left: $menu-width; + } + } + &.right-menu { + & > .main-view { + padding-right: $menu-width; + } + } + & > .main-view { + padding-top: $topbar-height; + } + ui-view { + height: inherit; + + & > * { + display: block; + padding: $spacing-md; + } + } + @media screen and (max-width: $mobile-width) { + & > vn-topbar { + & > .show-menu { + display: block; + } + } + &.left-menu { + & > vn-topbar { + left: 0; + } + & > .main-view { + padding-left: 0; + } + } + &.right-menu { + & > .main-view { + padding-right: 0; + } + } + ui-view > * { + padding-left: 0; + padding-right: 0; + } + } +} diff --git a/front/salix/components/left-menu/left-menu.html b/front/salix/components/left-menu/left-menu.html index 12cea49a3..73488a24b 100644 --- a/front/salix/components/left-menu/left-menu.html +++ b/front/salix/components/left-menu/left-menu.html @@ -1,17 +1,30 @@ -