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' save: 'vn-travel-create vn-submit > button'
}, },
travelExtraCommunity: { travelExtraCommunity: {
anySearchResult: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr', anySearchResult: 'vn-travel-extra-community > vn-card div > tbody > tr[ng-attr-id="{{::travel.id}}"]',
firstTravelReference: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="reference"]', firstTravelReference: 'vn-travel-extra-community tbody:nth-child(2) vn-textfield[ng-model="travel.ref"]',
firstTravelLockedKg: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="lockedKg"]', 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' removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i'
}, },
travelBasicData: { travelBasicData: {

View File

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

View File

@ -128,7 +128,7 @@ module.exports = Self => {
w.name AS warehouseInFk, w.name AS warehouseInFk,
w.name AS warehouseInName, w.name AS warehouseInName,
SUM(b.stickers) AS stickers, SUM(b.stickers) AS stickers,
s.id AS supplierFk, s.id AS cargoSupplierFk,
s.nickname AS cargoSupplierNickname, 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(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 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.travelFk,
e.ref, e.ref,
e.loadPriority, e.loadPriority,
s.id AS supplierFk,
s.name AS supplierName, s.name AS supplierName,
SUM(b.stickers) AS stickers, SUM(b.stickers) AS stickers,
e.evaNotes, e.evaNotes,

View File

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

View File

@ -1,6 +1,7 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="Travels/extraCommunityFilter" url="Travels/extraCommunityFilter"
filter="::$ctrl.filter"
data="travels" data="travels"
order="shipped ASC, landed ASC, travelFk, loadPriority, agencyModeFk, evaNotes" order="shipped ASC, landed ASC, travelFk, loadPriority, agencyModeFk, evaNotes"
limit="20" limit="20"
@ -18,112 +19,174 @@
model="model"> model="model">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-data-viewer model="model" class="travel-list"> <vn-card class="travel-list scrollable">
<vn-card> <smart-table
<section class="vn-pa-md"> model="model"
options="$ctrl.smartTableOptions">
<slot-actions>
<section>
<vn-tool-bar class="vn-mb-md"> <vn-tool-bar class="vn-mb-md">
<vn-button disabled="!$ctrl.hasDateRange" <vn-button
disabled="!$ctrl.hasDateRange"
icon="picture_as_pdf" icon="picture_as_pdf"
ng-click="$ctrl.showReport()" ng-click="$ctrl.showReport()"
vn-tooltip="Open as PDF"> vn-tooltip="Open as PDF">
</vn-button> </vn-button>
</vn-tool-bar> </vn-tool-bar>
<vn-table> </section>
<vn-thead> </slot-actions>
<vn-tr> <slot-table>
<vn-th shrink>Id</vn-th> <table>
<vn-th expand>Supplier</vn-th> <thead>
<vn-th expand>Freighter</vn-th> <tr>
<vn-th>Reference</vn-th> <th field="id" shrink>
<vn-th number>Packages</vn-th> <span translate>Id</span>
<vn-th number>Bl. KG</vn-th> </th>
<vn-th number>Phy. KG</vn-th> <th field="cargoSupplierFk" expand>
<vn-th number>Vol. KG</vn-th> <span translate>Supplier</span>
<vn-th expand translate-attr="{title: 'Warehouse Out'}"> </th>
Wh. Out <th field="agencyModeFk" expand>
</vn-th> <span translate>Agency</span>
<vn-th expand>W. Shipped</vn-th> </th>
<vn-th expand translate-attr="{title: 'Warehouse In'}"> <th field="ref">
Wh. In <span translate>Reference</span>
</vn-th> </th>
<vn-th expand>W. Landed</vn-th> <th field="stickers" number>
</vn-tr> <span translate>Packages</span>
</vn-thead> </th>
<vn-tbody ng-repeat="travel in travels" class="vn-mb-md" vn-droppable="$ctrl.onDrop($event)" ng-attr-id="{{::travel.id}}"> <th field="kg" number>
<vn-tr class="header"> <span translate>Bl. KG</span>
<vn-td> </th>
<span class="link" <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)"> ng-click="travelDescriptor.show($event, travel.id)">
{{::travel.id}} {{::travel.id}}
</span> </span>
</vn-td> </td>
<vn-td expand>{{::travel.agencyModeName}}</vn-td> <td expand vn-click-stop>
<vn-td expand>{{::travel.cargoSupplierNickname}}</vn-td> <span
<vn-td-editable name="reference" expand> class="link"
<text>{{travel.ref}}</text> ng-click="supplierDescriptor.show($event, travel.cargoSupplierFk)">
<field> {{::travel.cargoSupplierNickname}}
<vn-textfield class="dense" vn-focus </span>
</td>
<td expand>{{::travel.agencyModeName}}</td>
<td
name="reference"
expand
vn-click-stop>
<vn-textfield
class="dense td-editable"
ng-model="travel.ref" ng-model="travel.ref"
on-change="$ctrl.save(travel.id, {ref: value})"> on-change="$ctrl.save(travel.id, {ref: value})">
</vn-textfield> </vn-textfield>
</field> </vn-icon>
</vn-td-editable> </td>
<vn-td number>{{::travel.stickers}}</vn-td> <td number>{{::travel.stickers}}</td>
<vn-td-editable name="lockedKg" expand style="text-align: right"> <td
<text number>{{travel.kg}}</text> name="lockedKg"
<field> expand
<vn-input-number class="dense" vn-focus vn-click-stop>
<vn-input-number
number
class="td-editable number"
ng-model="travel.kg" ng-model="travel.kg"
on-change="$ctrl.save(travel.id, {kg: value})" on-change="$ctrl.save(travel.id, {kg: value})"
min="0"> min="0">
</vn-input-number> </vn-input-number>
</field> </td>
</vn-td-editable> <td number>{{::travel.loadedKg}}</td>
<vn-td number>{{::travel.loadedKg}}</vn-td> <td number>{{::travel.volumeKg}}</td>
<vn-td number>{{::travel.volumeKg}}</vn-td> <td expand>{{::travel.warehouseOutName}}</td>
<vn-td expand>{{::travel.warehouseOutName}}</vn-td> <td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</td>
<vn-td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</vn-td> <td expand>{{::travel.warehouseInName}}</td>
<vn-td expand>{{::travel.warehouseInName}}</vn-td> <td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</td>
<vn-td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</vn-td> </tr>
</vn-tr> <tr
<a href="#" ng-repeat="entry in travel.entries" class="vn-tr" draggable ng-repeat="entry in travel.entries"
draggable
ng-attr-id="{{::entry.id}}" ng-attr-id="{{::entry.id}}"
ng-click="$event.preventDefault()"> ng-click="$event.preventDefault()">
<vn-td> <td>
<span class="link" <span class="link"
ng-click="entryDescriptor.show($event, entry.id)"> ng-click="entryDescriptor.show($event, entry.id)">
{{::entry.id}} {{::entry.id}}
</span> </span>
</vn-td> </td>
<vn-td>{{::entry.supplierName}}</vn-td> <td>
<vn-td></vn-td> <span
<vn-td expand>{{::entry.ref}}</vn-td> class="link"
<vn-td number>{{::entry.stickers}}</vn-td> ng-click="supplierDescriptor.show($event, entry.supplierFk)">
<vn-td number></vn-td> {{::entry.supplierName}}
<vn-td number>{{::entry.loadedkg}}</vn-td> </span>
<vn-td number>{{::entry.volumeKg}}</vn-td> </td>
<vn-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}}"> <span ng-if="::entry.notes" vn-tooltip="{{::entry.notes}}">
{{::entry.notes}} {{::entry.notes}}
</span> </span>
</vn-td> </td>
<vn-td> <td>
<span ng-if="::entry.evaNotes" vn-tooltip="{{::entry.evaNotes}}"> <span ng-if="::entry.evaNotes" vn-tooltip="{{::entry.evaNotes}}">
{{::entry.evaNotes}} {{::entry.evaNotes}}
</span> </span>
</vn-td> </td>
<vn-td></vn-td> <td></td>
<vn-td></vn-td> <td></td>
</a> </tr>
</vn-tbody> </tbody>
</vn-table> </table>
</section> </slot-table>
</smart-table>
</vn-card> </vn-card>
</vn-data-viewer>
<vn-travel-descriptor-popover <vn-travel-descriptor-popover
vn-id="travelDescriptor"> vn-id="travelDescriptor">
</vn-travel-descriptor-popover> </vn-travel-descriptor-popover>
<vn-entry-descriptor-popover <vn-entry-descriptor-popover
vn-id="entryDescriptor"> vn-id="entryDescriptor">
</vn-entry-descriptor-popover> </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', draggable.addEventListener('dragend',
event => this.dragEnd(event)); event => this.dragEnd(event));
this.draggableElement = 'a[draggable]'; draggable.addEventListener('dragover',
this.droppableElement = 'vn-tbody[vn-droppable]'; 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 twoDays = 2;
const shippedFrom = new Date(); const shippedFrom = new Date();
@ -32,6 +39,8 @@ class Controller extends Section {
landedTo: landedTo, landedTo: landedTo,
continent: 'AM' continent: 'AM'
}; };
this.smartTableOptions = {};
} }
get hasDateRange() { get hasDateRange() {
@ -44,6 +53,15 @@ class Controller extends Section {
return hasLanded || hasShipped || hasContinent || hasWarehouseOut; 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) { findDraggable($event) {
const target = $event.target; const target = $event.target;
const draggable = target.closest(this.draggableElement); const draggable = target.closest(this.draggableElement);
@ -65,6 +83,7 @@ class Controller extends Section {
const id = parseInt(draggable.id); const id = parseInt(draggable.id);
this.entryId = id; this.entryId = id;
this.entry = draggable; this.entry = draggable;
this.interval = setInterval(() => this.onDragInterval(), 50);
} }
dragEnd($event) { dragEnd($event) {
@ -72,6 +91,8 @@ class Controller extends Section {
draggable.classList.remove('dragging'); draggable.classList.remove('dragging');
this.entryId = null; this.entryId = null;
this.entry = null; this.entry = null;
clearInterval(this.interval);
} }
onDrop($event) { 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) { save(id, data) {
const endpoint = `Travels/${id}`; const endpoint = `Travels/${id}`;
this.$http.patch(endpoint, data) this.$http.patch(endpoint, data)

View File

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

View File

@ -15,41 +15,44 @@ vn-travel-extra-community {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
cursor: pointer;
} }
vn-td-editable text { table[vn-droppable] {
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] {
border-radius: 0; border-radius: 0;
} }
a[draggable] { tr[draggable] {
transition: all .5s; transition: all .5s;
cursor: move; cursor: move;
overflow: auto;
outline: 0; outline: 0;
height: 65px;
pointer-events: fill;
user-select:all;
} }
a[draggable]:hover { tr[draggable] *::selection{
background-color: $color-hover-cd background-color: transparent;
} }
a[draggable].dragging { tr[draggable]:hover {
background-color: $color-success-light; background-color: $color-hover-cd;
font-weight:bold }
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;
} }
} }