Merge pull request '4139-refactor(travel_extra-community): use smart-table, fix scroll' (#1052) from 4139-travel_extra-community_refactor into dev
gitea/salix/pipeline/head Build queued... Details

Reviewed-on: #1052
Reviewed-by: Joan Sanchez <joan@verdnatura.es>
This commit is contained in:
Joan Sanchez 2022-09-16 07:11:59 +00:00
commit 1b09f3bf7f
8 changed files with 262 additions and 139 deletions

View File

@ -1014,9 +1014,9 @@ export default {
save: 'vn-travel-create vn-submit > button'
},
travelExtraCommunity: {
anySearchResult: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr',
firstTravelReference: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="reference"]',
firstTravelLockedKg: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="lockedKg"]',
anySearchResult: 'vn-travel-extra-community > vn-card div > tbody > tr[ng-attr-id="{{::travel.id}}"]',
firstTravelReference: 'vn-travel-extra-community tbody:nth-child(2) vn-textfield[ng-model="travel.ref"]',
firstTravelLockedKg: 'vn-travel-extra-community tbody:nth-child(2) vn-input-number[ng-model="travel.kg"]',
removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i'
},
travelBasicData: {

View File

@ -19,18 +19,22 @@ describe('Travel extra community path', () => {
it('should edit the travel reference and the locked kilograms', async() => {
await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter);
await page.waitForSpinnerLoad();
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
await page.waitForSpinnerLoad();
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelLockedKg, '1500');
await page.clearInput(selectors.travelExtraCommunity.firstTravelReference);
await page.write(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
await page.clearInput(selectors.travelExtraCommunity.firstTravelLockedKg);
await page.write(selectors.travelExtraCommunity.firstTravelLockedKg, '1500');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the index and confirm the reference and locked kg were edited', async() => {
await page.accessToSection('travel.index');
await page.accessToSection('travel.extraCommunity');
await page.waitToClick(selectors.travelExtraCommunity.removeContinentFilter);
await page.waitForTextInElement(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
const reference = await page.getProperty(selectors.travelExtraCommunity.firstTravelReference, 'innerText');
const lockedKg = await page.getProperty(selectors.travelExtraCommunity.firstTravelLockedKg, 'innerText');
const reference = await page.waitToGetProperty(selectors.travelExtraCommunity.firstTravelReference, 'value');
const lockedKg = await page.waitToGetProperty(selectors.travelExtraCommunity.firstTravelLockedKg, 'value');
expect(reference).toContain('edited reference');
expect(lockedKg).toContain(1500);

View File

@ -128,7 +128,7 @@ module.exports = Self => {
w.name AS warehouseInFk,
w.name AS warehouseInName,
SUM(b.stickers) AS stickers,
s.id AS supplierFk,
s.id AS cargoSupplierFk,
s.nickname AS cargoSupplierNickname,
CAST(SUM(i.density * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as loadedKg,
CAST(SUM(167.5 * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 ) as DECIMAL(10,0)) as volumeKg
@ -160,6 +160,7 @@ module.exports = Self => {
e.travelFk,
e.ref,
e.loadPriority,
s.id AS supplierFk,
s.name AS supplierName,
SUM(b.stickers) AS stickers,
e.evaNotes,

View File

@ -50,15 +50,15 @@
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
label="Warehouse In"
ng-model="filter.warehouseInFk"
label="Warehouse Out"
ng-model="filter.warehouseOutFk"
url="Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one
label="Warehouse Out"
ng-model="filter.warehouseOutFk"
label="Warehouse In"
ng-model="filter.warehouseInFk"
url="Warehouses"
show-field="name"
value-field="id">

View File

@ -1,6 +1,7 @@
<vn-crud-model
vn-id="model"
url="Travels/extraCommunityFilter"
filter="::$ctrl.filter"
data="travels"
order="shipped ASC, landed ASC, travelFk, loadPriority, agencyModeFk, evaNotes"
limit="20"
@ -18,112 +19,174 @@
model="model">
</vn-searchbar>
</vn-portal>
<vn-data-viewer model="model" class="travel-list">
<vn-card>
<section class="vn-pa-md">
<vn-card class="travel-list scrollable">
<smart-table
model="model"
options="$ctrl.smartTableOptions">
<slot-actions>
<section>
<vn-tool-bar class="vn-mb-md">
<vn-button disabled="!$ctrl.hasDateRange"
<vn-button
disabled="!$ctrl.hasDateRange"
icon="picture_as_pdf"
ng-click="$ctrl.showReport()"
vn-tooltip="Open as PDF">
</vn-button>
</vn-tool-bar>
<vn-table>
<vn-thead>
<vn-tr>
<vn-th shrink>Id</vn-th>
<vn-th expand>Supplier</vn-th>
<vn-th expand>Freighter</vn-th>
<vn-th>Reference</vn-th>
<vn-th number>Packages</vn-th>
<vn-th number>Bl. KG</vn-th>
<vn-th number>Phy. KG</vn-th>
<vn-th number>Vol. KG</vn-th>
<vn-th expand translate-attr="{title: 'Warehouse Out'}">
Wh. Out
</vn-th>
<vn-th expand>W. Shipped</vn-th>
<vn-th expand translate-attr="{title: 'Warehouse In'}">
Wh. In
</vn-th>
<vn-th expand>W. Landed</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody ng-repeat="travel in travels" class="vn-mb-md" vn-droppable="$ctrl.onDrop($event)" ng-attr-id="{{::travel.id}}">
<vn-tr class="header">
<vn-td>
<span class="link"
</section>
</slot-actions>
<slot-table>
<table>
<thead>
<tr>
<th field="id" shrink>
<span translate>Id</span>
</th>
<th field="cargoSupplierFk" expand>
<span translate>Supplier</span>
</th>
<th field="agencyModeFk" expand>
<span translate>Agency</span>
</th>
<th field="ref">
<span translate>Reference</span>
</th>
<th field="stickers" number>
<span translate>Packages</span>
</th>
<th field="kg" number>
<span translate>Bl. KG</span>
</th>
<th field="loadedKg" number>
<span translate>Phy. KG</span>
</th>
<th field="volumeKg" number>
<span translate>Vol. KG</span>
</th>
<th
field="warehouseOutFk"
translate-attr="{title: 'Warehouse Out'}">
<span translate>Wh. Out</span>
</th>
<th field="shipped">
<span translate>W. Shipped</span>
</th>
<th
field="warehouseInFk"
translate-attr="{title: 'Warehouse In'}">
<span translate>Wh. In</span>
</th>
<th field="landed">
<span translate>W. Landed</span>
</th>
</tr>
</thead>
<tbody
ng-repeat="travel in travels"
class="vn-mb-md"
vn-droppable="$ctrl.onDrop($event)"
ng-attr-id="{{::travel.id}}"
vn-stop-click>
<tr
class="header"
vn-anchor="::{
state: 'travel.card.basicData',
params: {id: travel.id}
}">
<td vn-click-stop>
<span
class="link"
ng-click="travelDescriptor.show($event, travel.id)">
{{::travel.id}}
</span>
</vn-td>
<vn-td expand>{{::travel.agencyModeName}}</vn-td>
<vn-td expand>{{::travel.cargoSupplierNickname}}</vn-td>
<vn-td-editable name="reference" expand>
<text>{{travel.ref}}</text>
<field>
<vn-textfield class="dense" vn-focus
</td>
<td expand vn-click-stop>
<span
class="link"
ng-click="supplierDescriptor.show($event, travel.cargoSupplierFk)">
{{::travel.cargoSupplierNickname}}
</span>
</td>
<td expand>{{::travel.agencyModeName}}</td>
<td
name="reference"
expand
vn-click-stop>
<vn-textfield
class="dense td-editable"
ng-model="travel.ref"
on-change="$ctrl.save(travel.id, {ref: value})">
</vn-textfield>
</field>
</vn-td-editable>
<vn-td number>{{::travel.stickers}}</vn-td>
<vn-td-editable name="lockedKg" expand style="text-align: right">
<text number>{{travel.kg}}</text>
<field>
<vn-input-number class="dense" vn-focus
</vn-icon>
</td>
<td number>{{::travel.stickers}}</td>
<td
name="lockedKg"
expand
vn-click-stop>
<vn-input-number
number
class="td-editable number"
ng-model="travel.kg"
on-change="$ctrl.save(travel.id, {kg: value})"
min="0">
</vn-input-number>
</field>
</vn-td-editable>
<vn-td number>{{::travel.loadedKg}}</vn-td>
<vn-td number>{{::travel.volumeKg}}</vn-td>
<vn-td expand>{{::travel.warehouseOutName}}</vn-td>
<vn-td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td expand>{{::travel.warehouseInName}}</vn-td>
<vn-td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</vn-td>
</vn-tr>
<a href="#" ng-repeat="entry in travel.entries" class="vn-tr" draggable
</td>
<td number>{{::travel.loadedKg}}</td>
<td number>{{::travel.volumeKg}}</td>
<td expand>{{::travel.warehouseOutName}}</td>
<td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</td>
<td expand>{{::travel.warehouseInName}}</td>
<td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</td>
</tr>
<tr
ng-repeat="entry in travel.entries"
draggable
ng-attr-id="{{::entry.id}}"
ng-click="$event.preventDefault()">
<vn-td>
<td>
<span class="link"
ng-click="entryDescriptor.show($event, entry.id)">
{{::entry.id}}
</span>
</vn-td>
<vn-td>{{::entry.supplierName}}</vn-td>
<vn-td></vn-td>
<vn-td expand>{{::entry.ref}}</vn-td>
<vn-td number>{{::entry.stickers}}</vn-td>
<vn-td number></vn-td>
<vn-td number>{{::entry.loadedkg}}</vn-td>
<vn-td number>{{::entry.volumeKg}}</vn-td>
<vn-td>
</td>
<td>
<span
class="link"
ng-click="supplierDescriptor.show($event, entry.supplierFk)">
{{::entry.supplierName}}
</span>
</td>
<td></td>
<td expand>{{::entry.ref}}</td>
<td number>{{::entry.stickers}}</td>
<td number></td>
<td number>{{::entry.loadedkg}}</td>
<td number>{{::entry.volumeKg}}</td>
<td>
<span ng-if="::entry.notes" vn-tooltip="{{::entry.notes}}">
{{::entry.notes}}
</span>
</vn-td>
<vn-td>
</td>
<td>
<span ng-if="::entry.evaNotes" vn-tooltip="{{::entry.evaNotes}}">
{{::entry.evaNotes}}
</span>
</vn-td>
<vn-td></vn-td>
<vn-td></vn-td>
</a>
</vn-tbody>
</vn-table>
</section>
</vn-card>
</vn-data-viewer>
</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-travel-descriptor-popover
vn-id="travelDescriptor">
</vn-travel-descriptor-popover>
<vn-entry-descriptor-popover
vn-id="entryDescriptor">
</vn-entry-descriptor-popover>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>

View File

@ -14,8 +14,15 @@ class Controller extends Section {
draggable.addEventListener('dragend',
event => this.dragEnd(event));
this.draggableElement = 'a[draggable]';
this.droppableElement = 'vn-tbody[vn-droppable]';
draggable.addEventListener('dragover',
event => this.dragOver(event));
draggable.addEventListener('dragenter',
event => this.dragEnter(event));
draggable.addEventListener('dragleave',
event => this.dragLeave(event));
this.draggableElement = 'tr[draggable]';
this.droppableElement = 'tbody[vn-droppable]';
const twoDays = 2;
const shippedFrom = new Date();
@ -32,6 +39,8 @@ class Controller extends Section {
landedTo: landedTo,
continent: 'AM'
};
this.smartTableOptions = {};
}
get hasDateRange() {
@ -44,6 +53,15 @@ class Controller extends Section {
return hasLanded || hasShipped || hasContinent || hasWarehouseOut;
}
onDragInterval() {
if (this.dragClientY > 0 && this.dragClientY < 75)
this.$window.scrollTo(0, this.$window.scrollY - 10);
const maxHeight = window.screen.availHeight - (window.outerHeight - window.innerHeight);
if (this.dragClientY > maxHeight - 75 && this.dragClientY < maxHeight)
this.$window.scrollTo(0, this.$window.scrollY + 10);
}
findDraggable($event) {
const target = $event.target;
const draggable = target.closest(this.draggableElement);
@ -65,6 +83,7 @@ class Controller extends Section {
const id = parseInt(draggable.id);
this.entryId = id;
this.entry = draggable;
this.interval = setInterval(() => this.onDragInterval(), 50);
}
dragEnd($event) {
@ -72,6 +91,8 @@ class Controller extends Section {
draggable.classList.remove('dragging');
this.entryId = null;
this.entry = null;
clearInterval(this.interval);
}
onDrop($event) {
@ -91,6 +112,37 @@ class Controller extends Section {
}
}
undrop() {
if (!this.dropping) return;
this.dropping.classList.remove('dropping');
this.dropping = null;
}
dragOver($event) {
this.dragClientY = $event.clientY;
$event.preventDefault();
}
dragEnter($event) {
let element = this.findDroppable($event);
if (element) this.dropCount++;
if (element != this.dropping) {
this.undrop();
if (element) element.classList.add('dropping');
this.dropping = element;
}
}
dragLeave($event) {
let element = this.findDroppable($event);
if (element) {
this.dropCount--;
if (this.dropCount == 0) this.undrop();
}
}
save(id, data) {
const endpoint = `Travels/${id}`;
this.$http.patch(endpoint, data)

View File

@ -27,7 +27,7 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('findDraggable()', () => {
it('should find the draggable element', () => {
const draggable = document.createElement('a');
const draggable = document.createElement('tr');
draggable.setAttribute('draggable', true);
const $event = new Event('dragstart');
@ -43,7 +43,7 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('findDroppable()', () => {
it('should find the droppable element', () => {
const droppable = document.createElement('vn-tbody');
const droppable = document.createElement('tbody');
droppable.setAttribute('vn-droppable', true);
const $event = new Event('drop');
@ -60,7 +60,7 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('dragStart()', () => {
it(`should add the class "dragging" to the draggable element
and then set the entryId controller property`, () => {
const draggable = document.createElement('a');
const draggable = document.createElement('tr');
draggable.setAttribute('draggable', true);
draggable.setAttribute('id', 3);
@ -80,7 +80,7 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('dragEnd()', () => {
it(`should remove the class "dragging" from the draggable element
and then set the entryId controller property to null`, () => {
const draggable = document.createElement('a');
const draggable = document.createElement('tr');
draggable.setAttribute('draggable', true);
draggable.setAttribute('id', 3);
draggable.classList.add('dragging');
@ -100,13 +100,13 @@ describe('Travel Component vnTravelExtraCommunity', () => {
describe('onDrop()', () => {
it('should make an HTTP patch query', () => {
const droppable = document.createElement('vn-tbody');
const droppable = document.createElement('tbody');
droppable.setAttribute('vn-droppable', true);
droppable.setAttribute('id', 1);
jest.spyOn(controller, 'findDroppable').mockReturnValue(droppable);
const oldDroppable = document.createElement('vn-tbody');
const oldDroppable = document.createElement('tbody');
oldDroppable.setAttribute('vn-droppable', true);
const entry = document.createElement('div');
oldDroppable.appendChild(entry);

View File

@ -15,41 +15,44 @@ vn-travel-extra-community {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: pointer;
}
vn-td-editable text {
background-color: transparent;
padding: 0;
border: 0;
border-bottom: 1px dashed $color-active;
border-radius: 0;
color: $color-active
}
vn-td-editable:hover text:after {
font-family: 'Material Icons';
content: 'edit';
position: absolute;
right: -15px;
color: $color-spacer
}
vn-table[vn-droppable] {
table[vn-droppable] {
border-radius: 0;
}
a[draggable] {
tr[draggable] {
transition: all .5s;
cursor: move;
overflow: auto;
outline: 0;
height: 65px;
pointer-events: fill;
user-select:all;
}
a[draggable]:hover {
background-color: $color-hover-cd
tr[draggable] *::selection{
background-color: transparent;
}
a[draggable].dragging {
background-color: $color-success-light;
font-weight:bold
tr[draggable]:hover {
background-color: $color-hover-cd;
}
tr[draggable].dragging {
background-color: $color-primary-light;
color: $color-font-light;
font-weight:bold;
}
.td-editable{
input{
font-size: 1.25rem!important;
}
}
.number *{
text-align: right;
}
}