4139-refactor(travel_extra-community): use smart-table, fix scroll #1052

Merged
joan merged 3 commits from 4139-travel_extra-community_refactor into dev 2022-09-16 07:12:01 +00:00
8 changed files with 253 additions and 139 deletions
Showing only changes of commit 2b101cf7c1 - Show all commits

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,13 +160,14 @@ 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,
e.notes, e.notes,
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
FROM tmp.travel tr FROM tmp.travel tr
JOIN entry e ON e.travelFk = tr.id JOIN entry e ON e.travelFk = tr.id
JOIN buy b ON b.entryFk = e.id JOIN buy b ON b.entryFk = e.id
JOIN packaging pkg ON pkg.id = b.packageFk JOIN packaging pkg ON pkg.id = b.packageFk

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">
@ -84,4 +84,4 @@
<vn-submit label="Search"></vn-submit> <vn-submit label="Search"></vn-submit>
</vn-horizontal> </vn-horizontal>
</form> </form>
</div> </div>

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,165 @@
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"
<vn-tool-bar class="vn-mb-md"> options="$ctrl.smartTableOptions">
<vn-button disabled="!$ctrl.hasDateRange" <slot-actions>
icon="picture_as_pdf" <section>
ng-click="$ctrl.showReport()" <vn-tool-bar class="vn-mb-md">
vn-tooltip="Open as PDF"> <vn-button
</vn-button> disabled="!$ctrl.hasDateRange || $ctrl.$.model.userFilter"
</vn-tool-bar> icon="picture_as_pdf"
<vn-table> ng-click="$ctrl.showReport()"
<vn-thead> vn-tooltip="Open as PDF">
<vn-tr> </vn-button>
<vn-th shrink>Id</vn-th> </vn-tool-bar>
<vn-th expand>Supplier</vn-th> </section>
<vn-th expand>Freighter</vn-th> </slot-actions>
<vn-th>Reference</vn-th> <slot-table>
<vn-th number>Packages</vn-th> <table>
<vn-th number>Bl. KG</vn-th> <thead>
<vn-th number>Phy. KG</vn-th> <tr>
<vn-th number>Vol. KG</vn-th> <th field="id" shrink>
<vn-th expand translate-attr="{title: 'Warehouse Out'}"> <span translate>Id {{$ctrl.dragClientY}}</span>
Wh. Out </th>
</vn-th> <th field="cargoSupplierFk" expand>
<vn-th expand>W. Shipped</vn-th> <span translate>Supplier</span>
<vn-th expand translate-attr="{title: 'Warehouse In'}"> </th>
Wh. In <th field="agencyModeFk" expand>
</vn-th> <span translate>Freighter</span>
<vn-th expand>W. Landed</vn-th> </th>
</vn-tr> <th field="ref">
</vn-thead> <span translate>Reference</span>
<vn-tbody ng-repeat="travel in travels" class="vn-mb-md" vn-droppable="$ctrl.onDrop($event)" ng-attr-id="{{::travel.id}}"> </th>
<vn-tr class="header"> <th field="stickers" number>
<vn-td> <span translate>Packages</span>
<span class="link" </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">
<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>
ng-model="travel.ref" </td>
on-change="$ctrl.save(travel.id, {ref: value})"> <td expand>{{::travel.agencyModeName}}</td>
</vn-textfield> <td name="reference" expand vn-click-stop>
</field> <vn-textfield
</vn-td-editable> class="dense td-editable"
<vn-td number>{{::travel.stickers}}</vn-td> ng-model="travel.ref"
<vn-td-editable name="lockedKg" expand style="text-align: right"> on-change="$ctrl.save(travel.id, {ref: value})">
<text number>{{travel.kg}}</text> </vn-textfield>
<field> </vn-icon>
<vn-input-number class="dense" vn-focus </td>
ng-model="travel.kg" <td number>{{::travel.stickers}}</td>
on-change="$ctrl.save(travel.id, {kg: value})" <td name="lockedKg" expand>
min="0"> <vn-input-number
</vn-input-number> number
</field> class="td-editable number"
</vn-td-editable> ng-model="travel.kg"
<vn-td number>{{::travel.loadedKg}}</vn-td> on-change="$ctrl.save(travel.id, {kg: value})"
<vn-td number>{{::travel.volumeKg}}</vn-td> min="0"
<vn-td expand>{{::travel.warehouseOutName}}</vn-td> vn-click-stop>
<vn-td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</vn-td> </vn-input-number>
<vn-td expand>{{::travel.warehouseInName}}</vn-td> </td>
<vn-td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</vn-td> <td number>{{::travel.loadedKg}}</td>
</vn-tr> <td number>{{::travel.volumeKg}}</td>
<a href="#" ng-repeat="entry in travel.entries" class="vn-tr" draggable <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-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>
</vn-card> </smart-table>
</vn-data-viewer> </vn-card>
<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');
@ -58,9 +58,9 @@ 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);
@ -78,9 +78,9 @@ 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;
}
}