Merge branch 'testing' into feature/3096-testing_selenium

This commit is contained in:
Cayo Puigdefabregas 2022-05-24 16:53:12 +02:00
commit f6e115909f
8 changed files with 761 additions and 452 deletions

View file

@ -28,7 +28,7 @@
"class-methods-use-this": "off",
"eqeqeq": "warn",
"radix": "warn",
"max-classes-per-file": ["error", 2]
"max-classes-per-file": "warn"
},
"globals": {
"API_URLS": true,

View file

@ -8,6 +8,7 @@ ml).
## master
## testing
- [added] #273 Allow search/filter lots on lots management component.
## [2.1.1] - 2022-05-11
Hot fix release.

View file

@ -5,14 +5,11 @@ Revises: eca457d8b2a4
Create Date: 2021-03-15 17:40:34.410408
"""
import sqlalchemy as sa
import citext
import teal
from alembic import op
from alembic import context
import sqlalchemy as sa
from alembic import context, op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '51439cf24be8'
down_revision = '21afd375a654'
@ -36,59 +33,197 @@ def upgrade_data():
def upgrade():
## Trade
currency = sa.Enum('AFN', 'ARS', 'AWG', 'AUD', 'AZN', 'BSD', 'BBD', 'BDT', 'BYR', 'BZD', 'BMD',
'BOB', 'BAM', 'BWP', 'BGN', 'BRL', 'BND', 'KHR', 'CAD', 'KYD', 'CLP', 'CNY',
'COP', 'CRC', 'HRK', 'CUP', 'CZK', 'DKK', 'DOP', 'XCD', 'EGP', 'SVC', 'EEK',
'EUR', 'FKP', 'FJD', 'GHC', 'GIP', 'GTQ', 'GGP', 'GYD', 'HNL', 'HKD', 'HUF',
'ISK', 'INR', 'IDR', 'IRR', 'IMP', 'ILS', 'JMD', 'JPY', 'JEP', 'KZT', 'KPW',
'KRW', 'KGS', 'LAK', 'LVL', 'LBP', 'LRD', 'LTL', 'MKD', 'MYR', 'MUR', 'MXN',
'MNT', 'MZN', 'NAD', 'NPR', 'ANG', 'NZD', 'NIO', 'NGN', 'NOK', 'OMR', 'PKR',
'PAB', 'PYG', 'PEN', 'PHP', 'PLN', 'QAR', 'RON', 'RUB', 'SHP', 'SAR', 'RSD',
'SCR', 'SGD', 'SBD', 'SOS', 'ZAR', 'LKR', 'SEK', 'CHF', 'SRD', 'SYP', 'TWD',
'THB', 'TTD', 'TRY', 'TRL', 'TVD', 'UAH', 'GBP', 'USD', 'UYU', 'UZS', 'VEF', name='currency', create_type=False, checkfirst=True, schema=f'{get_inv()}')
# Trade
currency = sa.Enum(
'AFN',
'ARS',
'AWG',
'AUD',
'AZN',
'BSD',
'BBD',
'BDT',
'BYR',
'BZD',
'BMD',
'BOB',
'BAM',
'BWP',
'BGN',
'BRL',
'BND',
'KHR',
'CAD',
'KYD',
'CLP',
'CNY',
'COP',
'CRC',
'HRK',
'CUP',
'CZK',
'DKK',
'DOP',
'XCD',
'EGP',
'SVC',
'EEK',
'EUR',
'FKP',
'FJD',
'GHC',
'GIP',
'GTQ',
'GGP',
'GYD',
'HNL',
'HKD',
'HUF',
'ISK',
'INR',
'IDR',
'IRR',
'IMP',
'ILS',
'JMD',
'JPY',
'JEP',
'KZT',
'KPW',
'KRW',
'KGS',
'LAK',
'LVL',
'LBP',
'LRD',
'LTL',
'MKD',
'MYR',
'MUR',
'MXN',
'MNT',
'MZN',
'NAD',
'NPR',
'ANG',
'NZD',
'NIO',
'NGN',
'NOK',
'OMR',
'PKR',
'PAB',
'PYG',
'PEN',
'PHP',
'PLN',
'QAR',
'RON',
'RUB',
'SHP',
'SAR',
'RSD',
'SCR',
'SGD',
'SBD',
'SOS',
'ZAR',
'LKR',
'SEK',
'CHF',
'SRD',
'SYP',
'TWD',
'THB',
'TTD',
'TRY',
'TRL',
'TVD',
'UAH',
'GBP',
'USD',
'UYU',
'UZS',
'VEF',
name='currency',
create_type=False,
checkfirst=True,
)
op.drop_table('trade', schema=f'{get_inv()}')
op.create_table('trade',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('price', sa.Float(decimal_return_scale=4), nullable=True),
sa.Column('lot_id', postgresql.UUID(as_uuid=True), nullable=True),
sa.Column('date', sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column('user_from_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('user_to_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('document_id', citext.CIText(), nullable=True),
sa.Column('confirm', sa.Boolean(), nullable=True),
sa.Column('code', citext.CIText(), default='', nullable=True,
comment = "This code is used for traceability"),
sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ),
sa.ForeignKeyConstraint(['user_from_id'], ['common.user.id'], ),
sa.ForeignKeyConstraint(['user_to_id'], ['common.user.id'], ),
sa.ForeignKeyConstraint(['lot_id'], [f'{get_inv()}.lot.id'], ),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}'
)
op.create_table(
'trade',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('price', sa.Float(decimal_return_scale=4), nullable=True),
sa.Column('lot_id', postgresql.UUID(as_uuid=True), nullable=True),
sa.Column('date', sa.TIMESTAMP(timezone=True), nullable=True),
sa.Column('user_from_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('user_to_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('document_id', citext.CIText(), nullable=True),
sa.Column('confirm', sa.Boolean(), nullable=True),
sa.Column(
'code',
citext.CIText(),
default='',
nullable=True,
comment="This code is used for traceability",
),
sa.ForeignKeyConstraint(
['id'],
[f'{get_inv()}.action.id'],
),
sa.ForeignKeyConstraint(
['user_from_id'],
['common.user.id'],
),
sa.ForeignKeyConstraint(
['user_to_id'],
['common.user.id'],
),
sa.ForeignKeyConstraint(
['lot_id'],
[f'{get_inv()}.lot.id'],
),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}',
)
op.add_column("trade", sa.Column("currency", currency, nullable=False), schema=f'{get_inv()}')
op.add_column(
"trade", sa.Column("currency", currency, nullable=False), schema=f'{get_inv()}'
)
op.create_table(
'confirm',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('action_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(
['id'],
[f'{get_inv()}.action.id'],
),
sa.ForeignKeyConstraint(
['action_id'],
[f'{get_inv()}.action.id'],
),
sa.ForeignKeyConstraint(
['user_id'],
['common.user.id'],
),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}',
)
op.create_table('confirm',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('action_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ),
sa.ForeignKeyConstraint(['action_id'], [f'{get_inv()}.action.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['common.user.id'], ),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}'
)
## User
op.add_column('user', sa.Column('active', sa.Boolean(), default=True, nullable=True),
schema='common')
op.add_column('user', sa.Column('phantom', sa.Boolean(), default=False, nullable=True),
schema='common')
# User
op.add_column(
'user',
sa.Column('active', sa.Boolean(), default=True, nullable=True),
schema='common',
)
op.add_column(
'user',
sa.Column('phantom', sa.Boolean(), default=False, nullable=True),
schema='common',
)
upgrade_data()
@ -99,28 +234,57 @@ def upgrade():
def downgrade():
op.drop_table('confirm', schema=f'{get_inv()}')
op.drop_table('trade', schema=f'{get_inv()}')
op.create_table('trade',
sa.Column('shipping_date', sa.TIMESTAMP(timezone=True), nullable=True,
comment='When are the devices going to be ready \n \
for shipping?\n '),
sa.Column('invoice_number', citext.CIText(), nullable=True,
comment='The id of the invoice so they can be linked.'),
sa.Column('price_id', postgresql.UUID(as_uuid=True), nullable=True,
comment='The price set for this trade. \n \
op.create_table(
'trade',
sa.Column(
'shipping_date',
sa.TIMESTAMP(timezone=True),
nullable=True,
comment='When are the devices going to be ready \n \
for shipping?\n ',
),
sa.Column(
'invoice_number',
citext.CIText(),
nullable=True,
comment='The id of the invoice so they can be linked.',
),
sa.Column(
'price_id',
postgresql.UUID(as_uuid=True),
nullable=True,
comment='The price set for this trade. \n \
If no price is set it is supposed that the trade was\n \
not payed, usual in donations.\n '),
sa.Column('to_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('confirms_id', postgresql.UUID(as_uuid=True), nullable=True,
comment='An organize action that this association confirms. \
not payed, usual in donations.\n ',
),
sa.Column('to_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column(
'confirms_id',
postgresql.UUID(as_uuid=True),
nullable=True,
comment='An organize action that this association confirms. \
\n \n For example, a ``Sell`` or ``Rent``\n \
can confirm a ``Reserve`` action.\n '),
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(['confirms_id'], [f'{get_inv()}.organize.id'], ),
sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ),
sa.ForeignKeyConstraint(['price_id'], [f'{get_inv()}.price.id'], ),
sa.ForeignKeyConstraint(['to_id'], [f'{get_inv()}.agent.id'], ),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}'
)
can confirm a ``Reserve`` action.\n ',
),
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(
['confirms_id'],
[f'{get_inv()}.organize.id'],
),
sa.ForeignKeyConstraint(
['id'],
[f'{get_inv()}.action.id'],
),
sa.ForeignKeyConstraint(
['price_id'],
[f'{get_inv()}.price.id'],
),
sa.ForeignKeyConstraint(
['to_id'],
[f'{get_inv()}.agent.id'],
),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}',
)
op.drop_column('user', 'active', schema='common')
op.drop_column('user', 'phantom', schema='common')

View file

@ -5,12 +5,10 @@ Revises: 3eb50297c365
Create Date: 2020-12-29 20:19:46.981207
"""
from alembic import context
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
import teal
from alembic import context, op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = 'b4bd1538bad5'
@ -25,33 +23,71 @@ def get_inv():
raise ValueError("Inventory value is not specified")
return INV
def upgrade():
# op.execute("COMMIT")
op.execute("ALTER TYPE snapshotsoftware ADD VALUE 'WorkbenchDesktop'")
SOFTWARE = sa.Enum(
'Workbench',
'WorkbenchAndroid',
'AndroidApp',
'Web',
'DesktopApp',
'WorkbenchDesktop',
name='snapshotsoftware',
create_type=False,
checkfirst=True,
)
# Live action
op.drop_table('live', schema=f'{get_inv()}')
op.create_table('live',
op.create_table(
'live',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('serial_number', sa.Unicode(), nullable=True,
comment='The serial number of the Hard Disk in lower case.'),
sa.Column(
'serial_number',
sa.Unicode(),
nullable=True,
comment='The serial number of the Hard Disk in lower case.',
),
sa.Column('usage_time_hdd', sa.Interval(), nullable=True),
sa.Column('snapshot_uuid', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('software_version', teal.db.StrictVersionType(length=32), nullable=False),
sa.Column('licence_version', teal.db.StrictVersionType(length=32), nullable=False),
sa.Column('software', sa.Enum('Workbench', 'WorkbenchAndroid', 'AndroidApp', 'Web',
'DesktopApp', 'WorkbenchDesktop', name='snapshotsoftware'), nullable=False),
sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ),
sa.Column(
'software_version', teal.db.StrictVersionType(length=32), nullable=False
),
sa.Column(
'licence_version', teal.db.StrictVersionType(length=32), nullable=False
),
sa.Column('software', SOFTWARE, nullable=False),
sa.ForeignKeyConstraint(
['id'],
[f'{get_inv()}.action.id'],
),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}'
schema=f'{get_inv()}',
)
def downgrade():
op.drop_table('live', schema=f'{get_inv()}')
op.create_table('live',
op.create_table(
'live',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('serial_number', sa.Unicode(), nullable=True,
comment='The serial number of the Hard Disk in lower case.'),
sa.Column(
'serial_number',
sa.Unicode(),
nullable=True,
comment='The serial number of the Hard Disk in lower case.',
),
sa.Column('usage_time_hdd', sa.Interval(), nullable=True),
sa.Column('snapshot_uuid', postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(['id'], [f'{get_inv()}.action.id'], ),
sa.ForeignKeyConstraint(
['id'],
[f'{get_inv()}.action.id'],
),
sa.PrimaryKeyConstraint('id'),
schema=f'{get_inv()}'
schema=f'{get_inv()}',
)
op.execute(
"select e.enumlabel FROM pg_enum e JOIN pg_type t ON e.enumtypid = t.oid WHERE t.typname = 'snapshotsoftware'"
)

View file

@ -1,353 +1,357 @@
/**
* Template Name: NiceAdmin - v2.2.0
* Template URL: https://bootstrapmade.com/nice-admin-bootstrap-admin-html-template/
* Author: BootstrapMade.com
* License: https://bootstrapmade.com/license/
*/
(function () {
"use strict";
/**
* Easy selector helper function
*/
const select = (el, all = false) => {
el = el.trim()
if (all) {
return [...document.querySelectorAll(el)]
}
return document.querySelector(el)
}
/**
* Easy event listener function
*/
const on = (type, el, listener, all = false) => {
if (all) {
select(el, all).forEach(e => e.addEventListener(type, listener))
} else {
select(el, all).addEventListener(type, listener)
}
}
/**
* Easy on scroll event listener
*/
const onscroll = (el, listener) => {
el.addEventListener("scroll", listener)
}
/**
* Sidebar toggle
*/
if (select(".toggle-sidebar-btn")) {
on("click", ".toggle-sidebar-btn", (e) => {
select("body").classList.toggle("toggle-sidebar")
})
}
/**
* Search bar toggle
*/
if (select(".search-bar-toggle")) {
on("click", ".search-bar-toggle", (e) => {
select(".search-bar").classList.toggle("search-bar-show")
})
}
/**
* Navbar links active state on scroll
*/
const navbarlinks = select("#navbar .scrollto", true)
const navbarlinksActive = () => {
const position = window.scrollY + 200
navbarlinks.forEach(navbarlink => {
if (!navbarlink.hash) return
const section = select(navbarlink.hash)
if (!section) return
if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) {
navbarlink.classList.add("active")
} else {
navbarlink.classList.remove("active")
}
})
}
window.addEventListener("load", navbarlinksActive)
onscroll(document, navbarlinksActive)
/**
* Toggle .header-scrolled class to #header when page is scrolled
*/
const selectHeader = select("#header")
if (selectHeader) {
const headerScrolled = () => {
if (window.scrollY > 100) {
selectHeader.classList.add("header-scrolled")
} else {
selectHeader.classList.remove("header-scrolled")
}
}
window.addEventListener("load", headerScrolled)
onscroll(document, headerScrolled)
}
/**
* Back to top button
*/
const backtotop = select(".back-to-top")
if (backtotop) {
const toggleBacktotop = () => {
if (window.scrollY > 100) {
backtotop.classList.add("active")
} else {
backtotop.classList.remove("active")
}
}
window.addEventListener("load", toggleBacktotop)
onscroll(document, toggleBacktotop)
}
/**
* Initiate tooltips
*/
const tooltipTriggerList = [].slice.call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]"))
const tooltipList = tooltipTriggerList.map((tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl))
/**
* Initiate quill editors
*/
if (select(".quill-editor-default")) {
new Quill(".quill-editor-default", {
theme: "snow"
});
}
if (select(".quill-editor-bubble")) {
new Quill(".quill-editor-bubble", {
theme: "bubble"
});
}
if (select(".quill-editor-full")) {
new Quill(".quill-editor-full", {
modules: {
toolbar: [
[{
font: []
}, {
size: []
}],
["bold", "italic", "underline", "strike"],
[{
color: []
},
{
background: []
}
],
[{
script: "super"
},
{
script: "sub"
}
],
[{
list: "ordered"
},
{
list: "bullet"
},
{
indent: "-1"
},
{
indent: "+1"
}
],
["direction", {
align: []
}],
["link", "image", "video"],
["clean"]
]
},
theme: "snow"
});
}
/**
* Initiate Bootstrap validation check
*/
const needsValidation = document.querySelectorAll(".needs-validation")
Array.prototype.slice.call(needsValidation)
.forEach((form) => {
form.addEventListener("submit", (event) => {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add("was-validated")
}, false)
})
/**
* Initiate Datatables
*/
const datatables = select(".datatable", true)
datatables.forEach(datatable => {
new simpleDatatables.DataTable(datatable);
})
/**
* Autoresize echart charts
*/
const mainContainer = select("#main");
if (mainContainer) {
setTimeout(() => {
new ResizeObserver(() => {
select(".echart", true).forEach(getEchart => {
echarts.getInstanceByDom(getEchart).resize();
})
}).observe(mainContainer);
}, 200);
}
/**
* Avoid hide dropdown when user clicked inside
*/
document.getElementById("dropDownLotsSelector").addEventListener("click", event => {
event.stopPropagation();
})
/**
* Search form functionality
*/
window.addEventListener("DOMContentLoaded", () => {
const searchForm = document.getElementById("SearchForm")
const inputSearch = document.querySelector("#SearchForm > input")
const doSearch = true
searchForm.addEventListener("submit", (event) => {
event.preventDefault();
})
let timeoutHandler = setTimeout(() => { }, 1)
const dropdownList = document.getElementById("dropdown-search-list")
const defaultEmptySearch = document.getElementById("dropdown-search-list").innerHTML
inputSearch.addEventListener("input", (e) => {
clearTimeout(timeoutHandler)
const searchText = e.target.value
if (searchText == "") {
document.getElementById("dropdown-search-list").innerHTML = defaultEmptySearch;
return
}
let resultCount = 0;
function searchCompleted() {
resultCount++;
setTimeout(() => {
if (resultCount == 2 && document.getElementById("dropdown-search-list").children.length == 2) {
document.getElementById("dropdown-search-list").innerHTML = `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-x-lg"></i>
<span style="margin-right: 10px">Nothing found</span>
</li>`
}
}, 100)
}
timeoutHandler = setTimeout(async () => {
dropdownList.innerHTML = `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-laptop"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</li>
<li id="lotSearchLoader" class="dropdown-item">
<i class="bi bi-folder2"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</li>`;
try {
Api.search_device(searchText.toUpperCase()).then(devices => {
dropdownList.querySelector("#deviceSearchLoader").style = "display: none"
for (let i = 0; i < devices.length; i++) {
const device = devices[i];
// See: ereuse_devicehub/resources/device/models.py
const verboseName = `${device.type} ${device.manufacturer} ${device.model}`
const templateString = `
<li>
<a class="dropdown-item" href="${API_URLS.devices_detail.replace("ReplaceTEXT", device.devicehubID)}" style="display: flex; align-items: center;" href="#">
<i class="bi bi-laptop"></i>
<span style="margin-right: 10px">${verboseName}</span>
<span class="badge bg-secondary" style="margin-left: auto;">${device.devicehubID}</span>
</a>
</li>`;
dropdownList.innerHTML += templateString
if (i == 4) { // Limit to 4 resullts
break;
}
}
searchCompleted();
})
} catch (error) {
dropdownList.innerHTML += `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-x"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Error searching devices</span>
</div>
</li>`;
console.log(error);
}
try {
Api.get_lots().then(lots => {
dropdownList.querySelector("#lotSearchLoader").style = "display: none"
for (let i = 0; i < lots.length; i++) {
const lot = lots[i];
if (lot.name.toUpperCase().includes(searchText.toUpperCase())) {
const templateString = `
<li>
<a class="dropdown-item" href="${API_URLS.lots_detail.replace("ReplaceTEXT", lot.id)}" style="display: flex; align-items: center;" href="#">
<i class="bi bi-folder2"></i>
<span style="margin-right: 10px">${lot.name}</span>
</a>
</li>`;
dropdownList.innerHTML += templateString
if (i == 4) { // Limit to 4 resullts
break;
}
}
}
searchCompleted();
})
} catch (error) {
dropdownList.innerHTML += `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-x"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Error searching lots</span>
</div>
</li>`;
console.log(error);
}
}, 1000)
})
})
})();
/**
* Template Name: NiceAdmin - v2.2.0
* Template URL: https://bootstrapmade.com/nice-admin-bootstrap-admin-html-template/
* Author: BootstrapMade.com
* License: https://bootstrapmade.com/license/
*/
(function () {
"use strict";
/**
* Easy selector helper function
*/
const select = (el, all = false) => {
el = el.trim()
if (all) {
return [...document.querySelectorAll(el)]
}
return document.querySelector(el)
}
/**
* Easy event listener function
*/
const on = (type, el, listener, all = false) => {
if (all) {
select(el, all).forEach(e => e.addEventListener(type, listener))
} else {
select(el, all).addEventListener(type, listener)
}
}
/**
* Easy on scroll event listener
*/
const onscroll = (el, listener) => {
el.addEventListener("scroll", listener)
}
/**
* Sidebar toggle
*/
if (select(".toggle-sidebar-btn")) {
on("click", ".toggle-sidebar-btn", (e) => {
select("body").classList.toggle("toggle-sidebar")
})
}
/**
* Search bar toggle
*/
if (select(".search-bar-toggle")) {
on("click", ".search-bar-toggle", (e) => {
select(".search-bar").classList.toggle("search-bar-show")
})
}
/**
* Navbar links active state on scroll
*/
const navbarlinks = select("#navbar .scrollto", true)
const navbarlinksActive = () => {
const position = window.scrollY + 200
navbarlinks.forEach(navbarlink => {
if (!navbarlink.hash) return
const section = select(navbarlink.hash)
if (!section) return
if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) {
navbarlink.classList.add("active")
} else {
navbarlink.classList.remove("active")
}
})
}
window.addEventListener("load", navbarlinksActive)
onscroll(document, navbarlinksActive)
/**
* Toggle .header-scrolled class to #header when page is scrolled
*/
const selectHeader = select("#header")
if (selectHeader) {
const headerScrolled = () => {
if (window.scrollY > 100) {
selectHeader.classList.add("header-scrolled")
} else {
selectHeader.classList.remove("header-scrolled")
}
}
window.addEventListener("load", headerScrolled)
onscroll(document, headerScrolled)
}
/**
* Back to top button
*/
const backtotop = select(".back-to-top")
if (backtotop) {
const toggleBacktotop = () => {
if (window.scrollY > 100) {
backtotop.classList.add("active")
} else {
backtotop.classList.remove("active")
}
}
window.addEventListener("load", toggleBacktotop)
onscroll(document, toggleBacktotop)
}
/**
* Initiate tooltips
*/
const tooltipTriggerList = [].slice.call(document.querySelectorAll("[data-bs-toggle=\"tooltip\"]"))
const tooltipList = tooltipTriggerList.map((tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl))
/**
* Initiate quill editors
*/
if (select(".quill-editor-default")) {
new Quill(".quill-editor-default", {
theme: "snow"
});
}
if (select(".quill-editor-bubble")) {
new Quill(".quill-editor-bubble", {
theme: "bubble"
});
}
if (select(".quill-editor-full")) {
new Quill(".quill-editor-full", {
modules: {
toolbar: [
[{
font: []
}, {
size: []
}],
["bold", "italic", "underline", "strike"],
[{
color: []
},
{
background: []
}
],
[{
script: "super"
},
{
script: "sub"
}
],
[{
list: "ordered"
},
{
list: "bullet"
},
{
indent: "-1"
},
{
indent: "+1"
}
],
["direction", {
align: []
}],
["link", "image", "video"],
["clean"]
]
},
theme: "snow"
});
}
/**
* Initiate Bootstrap validation check
*/
const needsValidation = document.querySelectorAll(".needs-validation")
Array.prototype.slice.call(needsValidation)
.forEach((form) => {
form.addEventListener("submit", (event) => {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add("was-validated")
}, false)
})
/**
* Initiate Datatables
*/
const datatables = select(".datatable", true)
datatables.forEach(datatable => {
new simpleDatatables.DataTable(datatable);
})
/**
* Autoresize echart charts
*/
const mainContainer = select("#main");
if (mainContainer) {
setTimeout(() => {
new ResizeObserver(() => {
select(".echart", true).forEach(getEchart => {
echarts.getInstanceByDom(getEchart).resize();
})
}).observe(mainContainer);
}, 200);
}
/**
* Avoid hide dropdown when user clicked inside
*/
const dropdownLotSelector = document.getElementById("dropDownLotsSelector")
if (dropdownLotSelector != null) { // If exists selector it will set click event
dropdownLotSelector.addEventListener("click", event => {
event.stopPropagation();
})
}
/**
* Search form functionality
*/
window.addEventListener("DOMContentLoaded", () => {
const searchForm = document.getElementById("SearchForm")
const inputSearch = document.querySelector("#SearchForm > input")
const doSearch = true
searchForm.addEventListener("submit", (event) => {
event.preventDefault();
})
let timeoutHandler = setTimeout(() => { }, 1)
const dropdownList = document.getElementById("dropdown-search-list")
const defaultEmptySearch = document.getElementById("dropdown-search-list").innerHTML
inputSearch.addEventListener("input", (e) => {
clearTimeout(timeoutHandler)
const searchText = e.target.value
if (searchText == "") {
document.getElementById("dropdown-search-list").innerHTML = defaultEmptySearch;
return
}
let resultCount = 0;
function searchCompleted() {
resultCount++;
setTimeout(() => {
if (resultCount == 2 && document.getElementById("dropdown-search-list").children.length == 2) {
document.getElementById("dropdown-search-list").innerHTML = `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-x-lg"></i>
<span style="margin-right: 10px">Nothing found</span>
</li>`
}
}, 100)
}
timeoutHandler = setTimeout(async () => {
dropdownList.innerHTML = `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-laptop"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</li>
<li id="lotSearchLoader" class="dropdown-item">
<i class="bi bi-folder2"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</li>`;
try {
Api.search_device(searchText.toUpperCase()).then(devices => {
dropdownList.querySelector("#deviceSearchLoader").style = "display: none"
for (let i = 0; i < devices.length; i++) {
const device = devices[i];
// See: ereuse_devicehub/resources/device/models.py
const verboseName = `${device.type} ${device.manufacturer} ${device.model}`
const templateString = `
<li>
<a class="dropdown-item" href="${API_URLS.devices_detail.replace("ReplaceTEXT", device.devicehubID)}" style="display: flex; align-items: center;" href="#">
<i class="bi bi-laptop"></i>
<span style="margin-right: 10px">${verboseName}</span>
<span class="badge bg-secondary" style="margin-left: auto;">${device.devicehubID}</span>
</a>
</li>`;
dropdownList.innerHTML += templateString
if (i == 4) { // Limit to 4 resullts
break;
}
}
searchCompleted();
})
} catch (error) {
dropdownList.innerHTML += `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-x"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Error searching devices</span>
</div>
</li>`;
console.log(error);
}
try {
Api.get_lots().then(lots => {
dropdownList.querySelector("#lotSearchLoader").style = "display: none"
for (let i = 0; i < lots.length; i++) {
const lot = lots[i];
if (lot.name.toUpperCase().includes(searchText.toUpperCase())) {
const templateString = `
<li>
<a class="dropdown-item" href="${API_URLS.lots_detail.replace("ReplaceTEXT", lot.id)}" style="display: flex; align-items: center;" href="#">
<i class="bi bi-folder2"></i>
<span style="margin-right: 10px">${lot.name}</span>
</a>
</li>`;
dropdownList.innerHTML += templateString
if (i == 4) { // Limit to 4 resullts
break;
}
}
}
searchCompleted();
})
} catch (error) {
dropdownList.innerHTML += `
<li id="deviceSearchLoader" class="dropdown-item">
<i class="bi bi-x"></i>
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Error searching lots</span>
</div>
</li>`;
console.log(error);
}
}, 1000)
})
})
})();

View file

@ -1,5 +1,7 @@
"use strict";
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _classStaticPrivateFieldSpecGet(receiver, classConstructor, descriptor) { _classCheckPrivateStaticAccess(receiver, classConstructor); _classCheckPrivateStaticFieldDescriptor(descriptor, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
function _classCheckPrivateStaticFieldDescriptor(descriptor, action) { if (descriptor === undefined) { throw new TypeError("attempted to " + action + " private static field before its declaration"); } }
@ -328,11 +330,59 @@ function export_file(type_file) {
$("#exportAlertModal").click();
}
}
class lotsSearcher {
static enable() {
if (this.lotsSearchElement) this.lotsSearchElement.disabled = false;
}
static disable() {
if (this.lotsSearchElement) this.lotsSearchElement.disabled = true;
}
/**
* do search when lot change in the search input
*/
static doSearch(inputSearch) {
const lots = this.getListLots();
for (let i = 0; i < lots.length; i++) {
if (lot.innerText.toLowerCase().includes(inputSearch.toLowerCase())) {
lot.parentElement.style.display = "";
} else {
lot.parentElement.style.display = "none";
}
}
}
}
_defineProperty(lotsSearcher, "lots", []);
_defineProperty(lotsSearcher, "lotsSearchElement", null);
_defineProperty(lotsSearcher, "getListLots", () => {
let lotsList = document.getElementById("LotsSelector");
if (lotsList) {
// Apply filter to get only labels
return Array.from(lotsList.children).filter(item => item.querySelector("label"));
}
return [];
});
document.addEventListener("DOMContentLoaded", () => {
lotsSearcher.lotsSearchElement = document.getElementById("lots-search");
lotsSearcher.lotsSearchElement.addEventListener("input", e => {
lotsSearcher.doSearch(e.target.value);
});
});
/**
* Reactive lots button
*/
async function processSelectedDevices() {
class Actions {
constructor() {
@ -584,6 +634,7 @@ async function processSelectedDevices() {
document.getElementById("ApplyDeviceLots").classList.add("disabled");
try {
lotsSearcher.disable();
listHTML.html("<li style=\"text-align: center\"><div class=\"spinner-border text-info\" style=\"margin: auto\" role=\"status\"></div></li>");
const selectedDevices = await Api.get_devices(selectedDevicesID);
let lots = await Api.get_lots();
@ -614,6 +665,7 @@ async function processSelectedDevices() {
listHTML.html("");
lotsList.forEach(lot => templateLot(lot, selectedDevices, listHTML, actions));
lotsSearcher.enable();
} catch (error) {
console.log(error);
listHTML.html("<li style=\"color: red; text-align: center\">Error feching devices and lots<br>(see console for more details)</li>");

View file

@ -93,7 +93,7 @@ const selectorController = (action) => {
table.on("datatable.perpage", () => itemListCheckChanged());
table.on("datatable.update", () => itemListCheckChanged());
}
if (action == "softInit") {
softInit();
itemListCheckChanged();
@ -103,8 +103,8 @@ const selectorController = (action) => {
function itemListCheckChanged() {
alertInfoDevices.innerHTML = `Selected devices: ${TableController.getSelectedDevices().length}
${TableController.getAllDevices().length != TableController.getSelectedDevices().length
? `<a href="#" class="ml-3">Select all devices (${TableController.getAllDevices().length})</a>`
: "<a href=\"#\" class=\"ml-3\">Cancel selection</a>"
? `<a href="#" class="ml-3">Select all devices (${TableController.getAllDevices().length})</a>`
: "<a href=\"#\" class=\"ml-3\">Cancel selection</a>"
}`;
if (TableController.getSelectedDevices().length <= 0) {
@ -132,7 +132,7 @@ const selectorController = (action) => {
get_device_list();
}
btnSelectAll.addEventListener("click", event => {
const checkedState = event.target.checked;
TableController.getAllDevicesInCurrentPage().forEach(ckeckbox => { ckeckbox.checked = checkedState });
@ -317,6 +317,47 @@ function export_file(type_file) {
}
}
class lotsSearcher {
static lots = [];
static lotsSearchElement = null;
static getListLots = () => {
const lotsList = document.getElementById("LotsSelector")
if (lotsList) {
// Apply filter to get only labels
return Array.from(lotsList.children).filter(item => item.querySelector("label"));
}
return [];
}
static enable() {
if (this.lotsSearchElement) this.lotsSearchElement.disabled = false;
}
static disable() {
if (this.lotsSearchElement) this.lotsSearchElement.disabled = true;
}
/**
* do search when lot change in the search input
*/
static doSearch(inputSearch) {
const lots = this.getListLots();
for (let i = 0; i < lots.length; i++) {
if (lot.innerText.toLowerCase().includes(inputSearch.toLowerCase())) {
lot.parentElement.style.display = "";
} else {
lot.parentElement.style.display = "none";
}
}
}
}
document.addEventListener("DOMContentLoaded", () => {
lotsSearcher.lotsSearchElement = document.getElementById("lots-search");
lotsSearcher.lotsSearchElement.addEventListener("input", (e) => { lotsSearcher.doSearch(e.target.value) })
})
/**
* Reactive lots button
@ -438,7 +479,7 @@ async function processSelectedDevices() {
const tmpDiv = document.createElement("div")
tmpDiv.innerHTML = newRequest
const newTable = document.createElement("table")
newTable.innerHTML = tmpDiv.querySelector("table").innerHTML
newTable.classList = "table"
@ -557,6 +598,7 @@ async function processSelectedDevices() {
document.getElementById("ApplyDeviceLots").classList.add("disabled");
try {
lotsSearcher.disable()
listHTML.html("<li style=\"text-align: center\"><div class=\"spinner-border text-info\" style=\"margin: auto\" role=\"status\"></div></li>")
const selectedDevices = await Api.get_devices(selectedDevicesID);
let lots = await Api.get_lots();
@ -589,6 +631,7 @@ async function processSelectedDevices() {
listHTML.html("");
lotsList.forEach(lot => templateLot(lot, selectedDevices, listHTML, actions));
lotsSearcher.enable();
} catch (error) {
console.log(error);
listHTML.html("<li style=\"color: red; text-align: center\">Error feching devices and lots<br>(see console for more details)</li>");

View file

@ -87,7 +87,16 @@
<span class="caret"></span>
</button>
<span class="d-none" id="activeTradeModal" data-bs-toggle="modal" data-bs-target="#tradeLotModal"></span>
<ul class="dropdown-menu" aria-labelledby="btnLots" id="dropDownLotsSelector">
<div class="row w-100">
<div class="input-group mb-3 mx-2">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1"><i class="bi bi-search"></i></span>
</div>
<input type="text" class="form-control" id="lots-search" placeholder="search" aria-label="search" aria-describedby="basic-addon1">
</div>
</div>
<h6 class="dropdown-header">Select lots where to store the selected devices</h6>
<ul class="mx-3" id="LotsSelector"></ul>
<li><hr /></li>