diff --git a/front/core/components/index.js b/front/core/components/index.js index 86ab89212..44b8beb45 100644 --- a/front/core/components/index.js +++ b/front/core/components/index.js @@ -32,6 +32,7 @@ import './float-button'; import './icon-menu'; import './icon-button'; import './input-number'; +import './json-value'; import './label-value'; import './range'; import './input-time'; diff --git a/front/core/components/json-value/index.html b/front/core/components/json-value/index.html new file mode 100644 index 000000000..dc1c97709 --- /dev/null +++ b/front/core/components/json-value/index.html @@ -0,0 +1 @@ + diff --git a/front/core/components/json-value/index.js b/front/core/components/json-value/index.js new file mode 100644 index 000000000..c92227ed3 --- /dev/null +++ b/front/core/components/json-value/index.js @@ -0,0 +1,56 @@ +import ngModule from '../../module'; +import Component from 'core/lib/component'; +import './style.scss'; + +const maxStrLen = 25; + +/** + * Displays pretty JSON value. + * + * @property {*} value The value + */ +export default class Controller extends Component { + get value() { + return this._value; + } + + set value(value) { + this._value = value; + const span = this.element; + const formattedValue = this.formatValue(value); + span.textContent = formattedValue; + span.title = typeof value == 'string' && value.length > maxStrLen ? value : ''; + span.className = `js-${value == null ? 'null' : typeof value}`; + } + + formatValue(value) { + if (value == null) return '∅'; + switch (typeof value) { + case 'boolean': + return value ? '✓' : '✗'; + case 'string': + return value.length <= maxStrLen + ? value + : value.substring(0, maxStrLen) + '...'; + case 'object': + if (value instanceof Date) { + const hasZeroTime = + value.getHours() === 0 && + value.getMinutes() === 0 && + value.getSeconds() === 0; + const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss'; + return this.$filter('date')(value, format); + } else + return value; + default: + return value; + } + } +} + +ngModule.vnComponent('vnJsonValue', { + controller: Controller, + bindings: { + value: ' thead, + & > tbody, + & > tfoot, + & > vn-thead, + & > vn-tbody, + & > vn-tfoot, + & > .vn-thead, + & > .vn-tbody, + & > .vn-tfoot { & > * { display: table-row; @@ -111,14 +116,14 @@ vn-table { color: inherit; } } - a.vn-tbody { + & > a.vn-tbody { &.clickable { @extend %clickable; } } - vn-tbody > *, - .vn-tbody > *, - tbody > * { + & > vn-tbody > *, + & > .vn-tbody > *, + & > tbody > * { border-bottom: $border-thin; &:last-child { diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 79dfcef8c..1bb8b1705 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -9,63 +9,99 @@ limit="20" auto-load="true"> - + - - - - Date - User - Model - Action - Name - Changes - - - - - - {{::log.creationDate | date:'dd/MM/yyyy HH:mm'}} - - - {{::log.user.name || 'System' | translate}} + + + + + + + + + + + + + + + + + + + +
+ Action + + Model + + Date +
+ + {{::log.user.name}} - - - {{::log.changedModel}} - - - {{::$ctrl.actionsText[log.action]}} - - - {{::log.changedModelValue}} - - - - - - - - - - - - - - - - - -
FieldBeforeAfter
{{prop.name}}{{prop.old}}{{prop.new}}
-
- {{::log.description}} + + System + +
+ + {{::log.changedModel}} + + + {{::log.changedModelValue}} + + + #{{::log.changedModelId}} + + + {{::log.creationDate | date:'dd/MM/yyyy HH:mm'}} +
+ + {{::$ctrl.actionsText[log.action]}} + + +
+ + + + +
+ + + {{::prop.name}}: + , + +
+
+ {{::prop.name}}: + + + ← + +
+
+
+ + {{::log.description}} + + + No changes + +
- - - - +
diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index 1c54aa9b8..f1eedf72e 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -13,6 +13,12 @@ export default class Controller extends Section { delete: 'Deletes', select: 'Views' }; + this.actionsClass = { + insert: 'success', + update: 'warning', + delete: 'alert', + select: 'notice' + }; this.filter = { include: [{ relation: 'user', @@ -50,8 +56,8 @@ export default class Controller extends Section { for (const prop of props) { log.props.push({ name: locale[prop] || prop, - old: this.formatValue(oldValues[prop]), - new: this.formatValue(newValues[prop]) + old: this.castValue(oldValues[prop]), + new: this.castValue(newValues[prop]) }); } } @@ -61,31 +67,19 @@ export default class Controller extends Section { return !(this.changedModel && this.changedModelId); } - formatValue(value) { - let type = typeof value; + castValue(value) { + return typeof value === 'string' && validDate.test(value) + ? new Date(value) + : value; + } - if (type === 'string' && validDate.test(value)) { - value = new Date(value); - type = typeof value; - } + mainVal(prop, action) { + return action == 'delete' ? prop.old : prop.new; + } - switch (type) { - case 'boolean': - return value ? '✓' : '✗'; - case 'object': - if (value instanceof Date) { - const hasZeroTime = - value.getHours() === 0 && - value.getMinutes() === 0 && - value.getSeconds() === 0; - const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss'; - return this.$filter('date')(value, format); - } - else - return value; - default: - return value; - } + toggleAttributes(log, changesEl, force) { + log.expand = force; + changesEl.classList.toggle('expanded', force); } showWorkerDescriptor(event, workerId) { diff --git a/front/salix/components/log/locale/es.yml b/front/salix/components/log/locale/es.yml index d341095d8..142175888 100644 --- a/front/salix/components/log/locale/es.yml +++ b/front/salix/components/log/locale/es.yml @@ -13,3 +13,4 @@ Views: Visualiza System: Sistema note: nota Changes: Cambios +No changes: No hay cambios diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 68cd5a047..00b08df64 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -1,66 +1,127 @@ @import "variables"; vn-log { - vn-td { - vertical-align: initial !important; + .vn-table { + table-layout: fixed; + + & > thead, + & > tbody { + & > tr { + td, th { + &:first-child { + padding-left: 16px; + } + &:last-child { + padding-right: 16px; + } + } + } + } + & > thead > tr > th { + max-width: initial; + } + & > tbody { + border-bottom: 1px solid rgba(0, 0, 0, 0.3); + + &:last-child { + border-bottom: none; + } + & > tr { + border-bottom: none; + height: initial; + + & > td { + padding-top: 16px; + padding-bottom: 16px; + + &.action > .chip { + display: inline-block; + } + } + &.change-header > td { + padding-bottom: 0; + } + &.change-detail > td { + padding-top: 6px; + vertical-align: top; + } + } + } + th, td { + &.action, + &.user { + width: 90px; + } + &.date { + width: 120px; + text-align: right; + } + } + } + .model-value { + font-style: italic; + color: #c7bd2b; + } + .model-id { + color: $color-font-secondary; + font-size: .9em; } .changes { - display: none; - } - .label { + overflow: hidden; + background-color: rgba(255, 255, 255, .05); + border-radius: 4px; color: $color-font-secondary; - } - .value { - color: $color-font; - } + transition: max-height 150ms ease-in-out; + max-height: 28px; + position: relative; - @media screen and (max-width: 1570px) { - vn-table .expendable { + & > .expand-button, + & > .shrink-button { display: none; } - .changes { - padding-top: 10px; - display: block; + &.props { + padding-right: 24px; + + & > .expand-button, + & > .shrink-button { + position: absolute; + top: 6px; + right: 8px; + font-size: inherit; + float: right; + cursor: pointer; + } + & > .expand-button { + display: block; + } + &.expanded { + max-height: 500px; + padding-right: 0; + + & > .changes-wrapper { + text-overflow: initial; + white-space: initial; + } + & > .shrink-button { + display: block; + } + & > .expand-button { + display: none; + } + } } - } - .attributes { - width: 100%; + & > .changes-wrapper { + padding: 4px 6px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; - tr { - height: 10px; - - & > td { - padding: 2px; + & > .no-changes { + font-style: italic; } - & > td.field, - & > th.field { - width: 20%; - color: gray; - } - & > td.before, - & > th.before, - & > td.after, - & > th.after { - width: 40%; - white-space: pre-line; + .json-field { + text-transform: capitalize; } } } } -.ellipsis { - white-space: nowrap; - overflow: hidden; - max-width: 400px; - text-overflow: ellipsis; - display: inline-block; -} -.no-ellipsize, -[no-ellipsize] { - text-overflow: ''; - white-space: normal; - overflow: auto; -} -.alignSpan { - overflow: hidden; - display: inline-block; -}