Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4077-login_recover-password
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alex Moreno 2022-09-19 12:08:26 +02:00
commit cc57742766
15 changed files with 344 additions and 199 deletions

View File

@ -1,6 +1,6 @@
FROM mariadb:10.7.5 FROM mariadb:10.7.3
ENV MARIADB_ROOT_PASSWORD root ENV MYSQL_ROOT_PASSWORD root
ENV TZ Europe/Madrid ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
@ -22,7 +22,6 @@ COPY \
docker/docker-start.sh \ docker/docker-start.sh \
/usr/local/bin/ /usr/local/bin/
RUN chmod 775 /etc/mysql/conf.d/docker.cnf
RUN mkdir /mysql-data \ RUN mkdir /mysql-data \
&& chown -R mysql:mysql /mysql-data && chown -R mysql:mysql /mysql-data
@ -36,8 +35,6 @@ COPY \
dump/structure.sql \ dump/structure.sql \
dump/dumpedFixtures.sql \ dump/dumpedFixtures.sql \
./ ./
RUN chmod 775 config.ini
RUN gosu mysql docker-init.sh \ RUN gosu mysql docker-init.sh \
&& docker-dump.sh mysqlPlugins \ && docker-dump.sh mysqlPlugins \
&& docker-dump.sh mockDate \ && docker-dump.sh mockDate \

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

@ -35,6 +35,19 @@ module.exports = Self => {
) sub ) sub
ORDER BY percentage DESC`, [date, date], myOptions); ORDER BY percentage DESC`, [date, date], myOptions);
const wastesTotal = await Self.rawSql(`
SELECT *, 100 * dwindle / total AS percentage
FROM (
SELECT buyer,
sum(ws.saleTotal) AS total,
sum(ws.saleWaste) AS dwindle
FROM bs.waste ws
WHERE year = YEAR(TIMESTAMPADD(WEEK,-1, ?))
AND week = WEEK(TIMESTAMPADD(WEEK,-1, ?), 1)
GROUP BY buyer
) sub
ORDER BY percentage DESC`, [date, date], myOptions);
const details = []; const details = [];
for (let waste of wastes) { for (let waste of wastes) {
@ -55,6 +68,14 @@ module.exports = Self => {
buyerDetail.lines.push(waste); buyerDetail.lines.push(waste);
} }
for (let waste of details) {
let buyerTotal = wastesTotal.find(totals => {
return waste.buyer == totals.buyer;
});
Object.assign(waste, buyerTotal);
}
return details; return details;
}; };
}; };

View File

@ -12,6 +12,7 @@ describe('Item getWasteByWorker()', () => {
const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
expect(anyResult.buyer).toMatch(/(CharlesXavier|HankPym|DavidCharlesHaller)/); expect(anyResult.buyer).toMatch(/(CharlesXavier|HankPym|DavidCharlesHaller)/);
expect(anyResult.total).toBeGreaterThanOrEqual(1000);
expect(anyResult.lines.length).toBeGreaterThanOrEqual(3); expect(anyResult.lines.length).toBeGreaterThanOrEqual(3);
await tx.rollback(); await tx.rollback();

View File

@ -4,39 +4,46 @@
data="details"> data="details">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer model="model"> <vn-data-viewer model="model">
<section ng-repeat="detail in details" class="vn-pa-md"> <vn-card>
<vn-horizontal class="header"> <vn-table>
<h5><span translate>{{detail.buyer}}</span></h5> <vn-thead>
<vn-none> <vn-tr class="header">
<vn-icon <vn-th class="waste-family">Buyer</vn-th>
<vn-th class="waste-family">Family</vn-th>
<vn-th number>Percentage</vn-th>
<vn-th number>Dwindle</vn-th>
<vn-th number>Total</vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody ng-repeat="detail in details" class="vn-mb-md">
<vn-tr class="header">
<vn-td>{{::detail.buyer}}</vn-td>
<vn-td>{{::detail.family}}</vn-td>
<vn-td number>{{::(detail.percentage / 100) | percentage: 2}}</vn-td>
<vn-td number>{{::detail.dwindle | currency: 'EUR'}}</vn-td>
<vn-td number>{{::detail.total | currency: 'EUR'}}</vn-td>
<vn-td shrink>
<vn-icon-button
ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}" ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}"
class="arrow pointer" class="arrow pointer"
icon="keyboard_arrow_up" icon="keyboard_arrow_up"
vn-tooltip="Minimize/Maximize" vn-tooltip="Minimize/Maximize"
ng-click="$ctrl.toggleHidePanel(detail)"> ng-click="$ctrl.toggleHidePanel(detail)">
</vn-icon> </vn-icon-button>
</vn-none> </vn-td>
</vn-horizontal>
<vn-card ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th class="waste-family">Family</vn-th>
<vn-th number>Percentage</vn-th>
<vn-th number>Dwindle</vn-th>
<vn-th number>Total</vn-th>
</vn-tr> </vn-tr>
</vn-thead> <vn-tr ng-repeat="waste in detail.lines" class="clickable vn-tr"
<vn-tbody> ui-sref="item.waste.detail({buyer: waste.buyer, family: waste.family})"
<a ng-repeat="waste in detail.lines" class="clickable vn-tr" ng-class="{'hidden': !$ctrl.wasteConfig[detail.buyer].hidden}">
ui-sref="item.waste.detail({buyer: waste.buyer, family: waste.family})"> <vn-td></vn-td>
<vn-td class="waste-family">{{::waste.family}}</vn-td> <vn-td>{{::waste.family}}</vn-td>
<vn-td number>{{::(waste.percentage / 100) | percentage: 2}}</vn-td> <vn-td number>{{::(waste.percentage / 100) | percentage: 2}}</vn-td>
<vn-td number>{{::waste.dwindle | currency: 'EUR'}}</vn-td> <vn-td number>{{::waste.dwindle | currency: 'EUR'}}</vn-td>
<vn-td number>{{::waste.total | currency: 'EUR'}}</vn-td> <vn-td number>{{::waste.total | currency: 'EUR'}}</vn-td>
<vn-td shrink></vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
</vn-card> </vn-card>
</section>
</vn-data-viewer> </vn-data-viewer>

View File

@ -5,20 +5,9 @@ vn-item-waste-index,
vn-item-waste-detail { vn-item-waste-detail {
.header { .header {
padding: 12px 0 5px 0; padding: 12px 0 5px 0;
color: gray; background-color: $color-bg;
font-size: 1.2rem; font-size: 1.2rem;
border-bottom: $border;
margin-bottom: 10px; margin-bottom: 10px;
& > vn-none > vn-icon {
@extend %clickable-light;
color: $color-button;
font-size: 1.4rem;
}
vn-none > .arrow {
transition: transform 200ms;
}
} }
vn-table vn-th.waste-family, vn-table vn-th.waste-family,
@ -26,11 +15,12 @@ vn-item-waste-detail {
max-width: 64px; max-width: 64px;
width: 64px width: 64px
} }
.hidden { .hidden {
display: none; display: none;
} }
.header > vn-none > .arrow.hidden {
.arrow.hidden {
display: block; display: block;
transform: rotate(180deg); transform: rotate(180deg);
} }

View File

@ -50,7 +50,9 @@
}, },
"scope": { "scope": {
"where": { "where": {
"isActive": true "isActive": {
"neq": false
}
} }
}, },
"acls": [ "acls": [

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>
</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');
@ -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;
} }
} }

View File

@ -1,6 +1,10 @@
{ {
"name": "ZoneExclusion", "name": "ZoneExclusion",
"base": "VnModel", "base": "Loggable",
"log": {
"model":"ZoneLog",
"relation": "zone"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "zoneExclusion" "table": "zoneExclusion"