231801_test_to_master #1519

Merged
alexm merged 490 commits from 231801_test_to_master into master 2023-05-12 06:29:59 +00:00
7 changed files with 396 additions and 152 deletions
Showing only changes of commit 0c8fe855ac - Show all commits

View File

@ -2,6 +2,7 @@
$font-size: 11pt;
$menu-width: 256px;
$right-menu-width: 315px;
$topbar-height: 56px;
$mobile-width: 800px;
$float-spacing: 20px;

View File

@ -88,13 +88,13 @@ vn-layout {
}
&.right-menu {
& > vn-topbar > .end {
width: 80px + $menu-width;
width: 80px + $right-menu-width;
}
& > .main-view {
padding-right: $menu-width;
padding-right: $right-menu-width;
}
[fixed-bottom-right] {
right: $menu-width;
right: $right-menu-width;
}
}
& > .main-view {

View File

@ -1,136 +1,215 @@
<vn-crud-model
url="Tags"
fields="['id','name','isFree', 'sourceTable']"
data="tags"
auto-load="true">
</vn-crud-model>
<div class="search-panel">
<form class="vn-pa-lg" ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="General search"
ng-model="filter.search"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
url="ItemCategories"
label="Category"
show-field="name"
value-field="id"
ng-model="filter.categoryFk">
</vn-autocomplete>
<vn-autocomplete vn-one
url="ItemTypes"
label="Type"
where="{categoryFk: filter.categoryFk}"
show-field="name"
value-field="id"
ng-model="filter.typeFk"
fields="['categoryFk']"
include="'category'">
<tpl-item>
<div>{{name}}</div>
<div class="text-caption text-secondary">
{{category.name}}
</div>
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="filter.buyerFk"
url="Workers/activeWithRole"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Warehouse"
ng-model="filter.warehouseFk"
url="Warehouses">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="Started"
ng-model="filter.started">
</vn-date-picker>
<vn-date-picker
vn-one
label="Ended"
ng-model="filter.ended">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-check vn-one
triple-state="true"
label="For me"
ng-model="filter.mine">
</vn-check>
<vn-check vn-one
triple-state="true"
label="Minimum price"
ng-model="filter.hasMinPrice">
</vn-check>
</vn-horizontal>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
Tags
</vn-one>
<vn-icon-button
vn-none
vn-bind="+"
vn-tooltip="Add tag"
icon="add_circle"
ng-click="filter.tags.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal ng-repeat="itemTag in filter.tags">
<vn-autocomplete vn-two vn-id="tag"
label="Tag"
initial-data="itemTag.tag"
ng-model="itemTag.tagFk"
data="tags"
show-field="name"
on-change="itemTag.value = null"
rule>
</vn-autocomplete>
<vn-textfield vn-three
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
vn-id="text"
label="Value"
ng-model="itemTag.value"
rule>
</vn-textfield>
<vn-autocomplete vn-three
ng-show="tag.selection.isFree === false"
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
search-function="{value: $search}"
label="Value"
ng-model="itemTag.value"
show-field="value"
value-field="value"
rule>
</vn-autocomplete>
<vn-icon-button
vn-none
vn-tooltip="Remove tag"
icon="delete"
ng-click="filter.tags.splice($index, 1)"
tabindex="-1">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>
<vn-crud-model url="Tags" fields="['id','name','isFree']" data="$ctrl.tags" auto-load="true" />
<vn-crud-model url="ItemCategories" data="categories" auto-load="true" />
<vn-side-menu side="right">
<vn-horizontal class="input">
<vn-textfield
label="General search"
ng-model="$ctrl.filter.search"
info="Search items by id, name or barcode"
vn-focus
ng-keydown="$ctrl.onKeyPress($event)">
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="item-category">
<vn-autocomplete
vn-id="category"
data="categories"
ng-model="$ctrl.filter.categoryFk"
show-field="name"
value-field="id"
label="Category">
</vn-autocomplete>
<vn-one ng-repeat="category in categories">
<vn-icon
ng-class="{'active': $ctrl.filter.categoryFk == category.id}"
icon="{{::category.icon}}"
vn-tooltip="{{::category.name}}"
ng-click="$ctrl.changeCategory(category.id)">
</vn-icon>
</vn-one>
</vn-horizontal>
<vn-vertical class="input">
<vn-autocomplete
vn-id="type"
disabled="!$ctrl.filter.categoryFk"
url="ItemTypes"
label="Type"
where="{categoryFk: $ctrl.filter.categoryFk}"
show-field="name"
value-field="id"
ng-model="$ctrl.filter.typeFk"
fields="['categoryFk']"
include="'category'"
on-change="$ctrl.addFilters()">
<tpl-item>
<div>{{name}}</div>
<div class="text-caption text-secondary">
{{category.name}}
</div>
</tpl-item>
</vn-autocomplete>
</vn-vertical>
<vn-horizontal class="input horizontal">
<vn-autocomplete
vn-id="buyer"
disabled="false"
ng-model="$ctrl.filter.buyerFk"
url="Workers/activeWithRole"
show-field="nickname"
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer"
on-change="$ctrl.addFilters()">
</vn-autocomplete>
<vn-autocomplete
vn-id="warehouse"
label="Warehouse"
ng-model="$ctrl.filter.warehouseFk"
url="Warehouses"
show-field="name"
value-field="id"
on-change="$ctrl.addFilters()">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="input horizontal">
<vn-date-picker
label="From"
ng-model="$ctrl.filter.started"
on-change="$ctrl.addFilters()">
</vn-date-picker>
<vn-date-picker
label="To"
ng-model="$ctrl.filter.ended"
on-change="$ctrl.addFilters()">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal class="input horizontal">
<vn-check
label="For me"
ng-model="$ctrl.filter.mine"
triple-state="true"
ng-click="$ctrl.addFilters()">
</vn-check>
<vn-check
label="Minimum price"
ng-model="$ctrl.filter.hasMinPrice"
triple-state="true"
ng-click="$ctrl.addFilters()">
</vn-check>
</vn-horizontal>
<vn-horizontal class="tags">
<vn-one class="text-subtitle1" translate> Tags </vn-one>
<vn-icon-button
vn-none
vn-tooltip="Add tag"
icon="add_circle"
ng-click="$ctrl.filter.tags.push({})">
</vn-icon-button>
</vn-horizontal>
<vn-horizontal class="tags horizontal" ng-repeat="itemTag in $ctrl.filter.tags">
<vn-autocomplete
vn-id="tag"
data="$ctrl.tags"
ng-model="itemTag.tagFk"
show-field="name"
label="Tag"
on-change="itemTag.value = null">
</vn-autocomplete>
<vn-textfield
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
label="Value"
ng-model="itemTag.value"
ng-keydown="$ctrl.onKeyPress($event)">
</vn-textfield>
<vn-autocomplete
ng-show="tag.selection.isFree === false"
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
search-function="{value: $search}"
label="Value"
ng-model="itemTag.value"
show-field="value"
value-field="value"
on-change="$ctrl.addFilters()">
</vn-autocomplete>
<vn-icon-button
vn-none
vn-tooltip="Remove tag"
icon="delete"
ng-click="$ctrl.removeTag(itemTag)">
</vn-icon-button>
</vn-horizontal>
<div class="chips">
<vn-chip
ng-if="$ctrl.filter.search"
removable="true"
on-remove="$ctrl.removeItemFilter('search')"
class="colored">
<span>Id/{{$ctrl.$t('Name')}}: {{$ctrl.filter.search}}</span>
</vn-chip>
<vn-chip
ng-if="category.selection"
removable="true"
on-remove="$ctrl.removeItemFilter('categoryFk')"
class="colored">
<span>{{$ctrl.$t('Category')}}: {{category.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="type.selection"
removable="true"
on-remove="$ctrl.removeItemFilter('typeFk')"
class="colored">
<span>{{$ctrl.$t('Type')}}: {{type.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="buyer.selection"
removable="true"
on-remove="$ctrl.removeItemFilter('buyerFk')"
class="colored">
<span>{{$ctrl.$t('Buyer')}}: {{buyer.selection.nickname}}</span>
</vn-chip>
<vn-chip
ng-if="warehouse.selection"
removable="true"
on-remove="$ctrl.removeItemFilter('warehouseFk')"
class="colored">
<span>{{$ctrl.$t('Warehouse')}}: {{warehouse.selection.name}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.started"
removable="true"
on-remove="$ctrl.removeItemFilter('started')"
class="colored">
<span>{{$ctrl.$t('Started')}}: {{$ctrl.filter.started | date:'dd/MM/yyyy'}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.ended"
removable="true"
on-remove="$ctrl.removeItemFilter('ended')"
class="colored">
<span>{{$ctrl.$t('Ended')}}: {{$ctrl.filter.ended | date:'dd/MM/yyyy'}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.mine != null"
removable="true"
on-remove="$ctrl.removeItemFilter('mine')"
class="colored">
<span>{{$ctrl.$t('For me')}}: {{$ctrl.filter.mine ? '✓' : '✗'}}</span>
</vn-chip>
<vn-chip
ng-if="$ctrl.filter.hasMinPrice != null"
removable="true"
on-remove="$ctrl.removeItemFilter('hasMinPrice')"
class="colored">
<span>{{$ctrl.$t('Minimum price')}}: {{$ctrl.filter.hasMinPrice ? '✓' : '✗'}}</span>
</vn-chip>
<vn-chip
ng-repeat="chipTag in $ctrl.filter.tags"
removable="true"
on-remove="$ctrl.removeTag(chipTag)"
class="colored"
ng-if="chipTag.value">
<span>{{$ctrl.showTagInfo(chipTag)}}</span>
</vn-chip>
</div>
</vn-side-menu>

View File

@ -1,19 +1,60 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
import './style.scss';
class Controller extends SearchPanel {
get filter() {
return this.$.filter;
constructor($element, $) {
super($element, $);
}
set filter(value = {}) {
if (!value.tags) value.tags = [{}];
$onInit() {
this.filter = {
tags: []
};
}
this.$.filter = value;
changeCategory(id) {
if (this.filter.categoryFk != id) {
this.filter.categoryFk = id;
this.addFilters();
}
}
removeItemFilter(param) {
this.filter[param] = null;
if (param == 'categoryFk') this.filter['typeFk'] = null;
this.addFilters();
}
removeTag(tag) {
const index = this.filter.tags.indexOf(tag);
if (index > -1) this.filter.tags.splice(index, 1);
this.addFilters();
}
onKeyPress($event) {
if ($event.key === 'Enter')
this.addFilters();
}
addFilters() {
for (let i = 0; i < this.filter.tags.length; i++) {
if (!this.filter.tags[i].value)
this.filter.tags.splice(i, 1);
}
return this.model.addFilter({}, this.filter);
}
showTagInfo(itemTag) {
if (!itemTag.tagFk) return itemTag.value;
return `${this.tags.find(tag => tag.id == itemTag.tagFk).name}: ${itemTag.value}`;
}
}
ngModule.vnComponent('vnFixedPriceSearchPanel', {
template: require('./index.html'),
controller: Controller
controller: Controller,
bindings: {
model: '<'
}
});

View File

@ -0,0 +1,56 @@
import './index.js';
describe('Item', () => {
describe('Component vnFixedPriceSearchPanel', () => {
let $element;
let controller;
beforeEach(ngModule('item'));
beforeEach(angular.mock.inject($componentController => {
$element = angular.element(`<vn-fixed-price-search-panel></vn-fixed-price-search-panel>`);
controller = $componentController('vnFixedPriceSearchPanel', {$element});
controller.model = {addFilter: () => {}};
}));
describe('removeItemFilter()', () => {
it(`should remove param from filter`, () => {
controller.filter = {tags: [], categoryFk: 1, typeFk: 1};
const expectFilter = {tags: [], categoryFk: null, typeFk: null};
controller.removeItemFilter('categoryFk');
expect(controller.filter).toEqual(expectFilter);
});
});
describe('removeTag()', () => {
it(`should remove tag from filter`, () => {
const tag = {tagFk: 1, value: 'Value'};
controller.filter = {tags: [tag]};
const expectFilter = {tags: []};
controller.removeTag(tag);
expect(controller.filter).toEqual(expectFilter);
});
});
describe('showTagInfo()', () => {
it(`should show tag value`, () => {
const tag = {value: 'Value'};
const result = controller.showTagInfo(tag);
expect(result).toEqual('Value');
});
it(`should show tag name and value`, () => {
const tag = {tagFk: 1, value: 'Value'};
controller.tags = [{id: 1, name: 'tagName'}];
const result = controller.showTagInfo(tag);
expect(result).toEqual('tagName: Value');
});
});
});
});

View File

@ -0,0 +1,71 @@
@import "variables";
vn-fixed-price-search-panel vn-side-menu {
.menu {
min-width: $right-menu-width;
}
& > div {
.input {
padding-left: $spacing-md;
padding-right: $spacing-md;
border-color: $color-spacer;
border-bottom: $border-thin;
}
.horizontal {
padding-left: $spacing-md;
padding-right: $spacing-md;
grid-auto-flow: column;
grid-column-gap: $spacing-sm;
align-items: center;
}
.tags {
padding: $spacing-md;
padding-bottom: 0%;
padding-top: 0%;
align-items: center;
}
.chips {
display: flex;
flex-wrap: wrap;
padding: $spacing-md;
overflow: hidden;
max-width: 100%;
border-color: $color-spacer;
border-top: $border-thin;
}
.item-category {
padding: $spacing-sm;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
vn-autocomplete[vn-id="category"] {
display: none;
}
& > vn-one {
padding: $spacing-sm;
min-width: 33.33%;
text-align: center;
box-sizing: border-box;
& > vn-icon {
padding: $spacing-sm;
background-color: $color-font-secondary;
border-radius: 50%;
cursor: pointer;
&.active {
background-color: $color-main;
color: #fff;
}
& > i:before {
font-size: 2.6rem;
width: 16px;
height: 16px;
}
}
}
}
}
}

View File

@ -13,14 +13,10 @@
order="name">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
auto-state="false"
panel="vn-fixed-price-search-panel"
info="Search prices by item ID or code"
placeholder="Search fixed prices"
model="model">
</vn-searchbar>
</vn-portal>
<vn-fixed-price-search-panel
model="model">
</vn-fixed-price-search-panel>
<div class="vn-w-xl">
<vn-card>
<smart-table