mirror of https://github.com/layters/testshop
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1041 lines
59 KiB
1041 lines
59 KiB
import QtQuick 2.12
|
|
import QtQuick.Controls 2.12
|
|
import QtQuick.Layouts 1.12
|
|
import Qt.labs.platform 1.1 // FileDialog
|
|
|
|
import FontAwesome 1.0
|
|
|
|
import "." as NeroshopComponents
|
|
|
|
Popup {
|
|
id: productDialog
|
|
implicitWidth: 800////parent.width
|
|
implicitHeight: 500//mainWindow.height - (mainWindow.header.height + mainWindow.footer.height)////500
|
|
visible: false
|
|
modal: true
|
|
closePolicy: Popup.CloseOnEscape
|
|
palette.text: (NeroshopComponents.Style.darkTheme) ? "#ffffff" : "#000000"
|
|
property string inputTextColor: palette.text
|
|
property real titleSpacing: 10 // space between title and input fields/controls
|
|
property string inputBaseColor: (NeroshopComponents.Style.darkTheme) ? (NeroshopComponents.Style.themeName == "PurpleDust" ? "#17171c" : "#1a1a1a") : "#fafafa"
|
|
property string inputBorderColor: (NeroshopComponents.Style.darkTheme) ? "#404040" : "#4d4d4d"
|
|
property real inputRadius: 10
|
|
property string optTextColor: "#708090"
|
|
|
|
background: Rectangle {
|
|
radius: 8
|
|
color: (NeroshopComponents.Style.darkTheme) ? (NeroshopComponents.Style.themeName == "PurpleDust" ? "#0e0e11" : "#101010") : "#f0f0f0"
|
|
|
|
// header
|
|
Rectangle {
|
|
id: titleBar
|
|
color: "transparent"
|
|
height: 40
|
|
width: parent.width
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
radius: 6
|
|
|
|
// Rounded top corners
|
|
Rectangle {
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
height: parent.height / 2
|
|
color: parent.color
|
|
}
|
|
|
|
Button {
|
|
id: closeButton
|
|
width: 25
|
|
height: this.width
|
|
|
|
anchors.verticalCenter: titleBar.verticalCenter
|
|
anchors.right: titleBar.right
|
|
anchors.rightMargin: 10
|
|
text: qsTr(FontAwesome.xmark)
|
|
contentItem: Text {
|
|
text: closeButton.text
|
|
color: "#ffffff"
|
|
font.bold: true
|
|
font.family: FontAwesome.fontFamily
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
background: Rectangle {
|
|
color: "#ff4d4d"
|
|
radius: 100
|
|
}
|
|
onClicked: {
|
|
productDialog.close()
|
|
mainScrollView.ScrollBar.vertical.position = 0.0 // reset scrollbar
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
contentItem: ScrollView {
|
|
id: mainScrollView
|
|
anchors.fill: parent
|
|
anchors.topMargin: titleBar.height + 20; anchors.bottomMargin: 20//anchors.topMargin////anchors.margins: 20
|
|
clip: true
|
|
ScrollBar.vertical.policy: ScrollBar.AlwaysOn//AsNeeded
|
|
ColumnLayout {
|
|
width: productDialog.availableWidth; height: productDialog.availableHeight
|
|
spacing: 30
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
// Product title
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Name or title"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
|
|
TextField {
|
|
id: productNameField
|
|
width: 500; height: 50
|
|
placeholderText: qsTr("Enter name")
|
|
color: productDialog.inputTextColor
|
|
selectByMouse: true
|
|
maximumLength: 200
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Product price (sales price)
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Price"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
|
|
TextField {
|
|
id: productPriceField
|
|
width: 500/* - parent.children[1].width - parent.spacing*/; height: 50//Layout.preferredWidth: 500 - parent.children[1].width - parent.spacing; Layout.preferredHeight: 50
|
|
placeholderText: qsTr("Enter price")
|
|
color: productDialog.inputTextColor
|
|
selectByMouse: true
|
|
validator: RegExpValidator{ regExp: new RegExp("^-?[0-9]+(\\.[0-9]{1," + Backend.getCurrencyDecimals(settingsDialog.currency.currentText) + "})?$") }
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
rightPadding: 25 + selectedCurrencyText.width
|
|
function adjustPriceDecimals() {
|
|
productPriceField.text = Number(productPriceField.text).toFixed(Backend.getCurrencyDecimals(settingsDialog.currency.currentText))
|
|
}
|
|
onEditingFinished: adjustPriceDecimals() // does not update when switching from crypto to fiat :(
|
|
|
|
Text {
|
|
id: selectedCurrencyText
|
|
text: settingsDialog.currency.currentText
|
|
color: productDialog.inputTextColor
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 15
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
font.bold: true
|
|
font.pointSize: 10
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
settingsDialog.currentIndex = 0 // Switch to General Settings tab
|
|
settingsDialog.open()
|
|
settingsDialog.currency.popup.open()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Product quantity
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Row {
|
|
spacing: 10
|
|
Text {
|
|
text: qsTr("Quantity")
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
Text {
|
|
text: qsTr(FontAwesome.questionCircle)
|
|
color: productDialog.optTextColor
|
|
font.bold: true
|
|
//font.pointSize:
|
|
anchors.verticalCenter: parent.children[0].verticalCenter
|
|
property bool hovered: false
|
|
NeroshopComponents.Hint {
|
|
x: parent.width + 10; y: ((parent.height - height) / 2) - 3
|
|
visible: parent.hovered
|
|
height: contentHeight + 20; width: contentWidth + 20
|
|
text: qsTr("Total number of items in stock")
|
|
pointer.visible: false;// delay: 0
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onEntered: parent.hovered = true
|
|
onExited: parent.hovered = false
|
|
}
|
|
}
|
|
}
|
|
|
|
TextField {
|
|
id: productQuantityField
|
|
width: 500; height: 50
|
|
placeholderText: qsTr("Enter quantity")
|
|
color: productDialog.inputTextColor
|
|
selectByMouse: true
|
|
inputMethodHints: Qt.ImhDigitsOnly // for Android and iOS - typically used for input of languages such as Chinese or Japanese
|
|
validator: RegExpValidator{ regExp: /[0-9]*/ }
|
|
text: "1"
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
rightPadding: 25 + quantityIncreaseText.contentWidth
|
|
function adjustQuantity() {
|
|
if(Number(productQuantityField.text) >= quantityIncreaseText.maximumQuantity) {
|
|
productQuantityField.text = quantityIncreaseText.maximumQuantity
|
|
}
|
|
if(Number(productQuantityField.text) <= quantityDecreaseText.minimumQuantity) {
|
|
productQuantityField.text = quantityDecreaseText.minimumQuantity
|
|
}
|
|
}
|
|
onEditingFinished: adjustQuantity()
|
|
|
|
Text {
|
|
id: quantityIncreaseText
|
|
text: qsTr("\uf0d8")
|
|
color: productDialog.inputTextColor
|
|
font.bold: true
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 15
|
|
anchors.top: parent.top; anchors.topMargin: 10
|
|
property real maximumQuantity: 999999999
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
if(Number(productQuantityField.text) >= parent.maximumQuantity) {
|
|
productQuantityField.text = parent.maximumQuantity
|
|
return;
|
|
}
|
|
productQuantityField.text = Number(productQuantityField.text) + 1
|
|
}
|
|
}
|
|
}
|
|
Text {
|
|
id: quantityDecreaseText
|
|
text: qsTr("\uf0d7")
|
|
color: productDialog.inputTextColor
|
|
font.bold: true
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 15
|
|
anchors.bottom: parent.bottom; anchors.bottomMargin: 10
|
|
property real minimumQuantity: 1
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
if(Number(productQuantityField.text) <= parent.minimumQuantity) {
|
|
productQuantityField.text = parent.minimumQuantity
|
|
return;
|
|
}
|
|
productQuantityField.text = Number(productQuantityField.text) - 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
width: 500; height: childrenRect.height
|
|
Text {
|
|
anchors.left: parent.left
|
|
anchors.verticalCenter: quantityPerOrderField.verticalCenter
|
|
text: qsTr("Limit quantity per order")
|
|
color: productDialog.palette.text
|
|
font.pointSize: 10
|
|
}
|
|
TextField {
|
|
id: quantityPerOrderField
|
|
anchors.right: parent.right
|
|
width: 50; height: 25
|
|
placeholderText: qsTr("#")
|
|
font.pixelSize: 12
|
|
color: productDialog.inputTextColor
|
|
selectByMouse: true
|
|
inputMethodHints: Qt.ImhDigitsOnly
|
|
validator: RegExpValidator{ regExp: /[0-9]*/ }
|
|
onTextChanged: {
|
|
if (text.length > 2) text = text.substring(0, 2);
|
|
}
|
|
onEditingFinished: {
|
|
if (text === "00") text = "";
|
|
if (text.startsWith("0")) text = text.substring(1);
|
|
}
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Product condition
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Condition"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
//visible: false
|
|
}
|
|
NeroshopComponents.ComboBox {
|
|
id: productConditionBox
|
|
width: 500; height: 50
|
|
model: ["New", "Used", "Refurbished (Renewed)", "Not applicable"] // default is New
|
|
Component.onCompleted: currentIndex = find("New")
|
|
radius: productDialog.inputRadius
|
|
color: productDialog.inputBaseColor
|
|
textColor: productDialog.inputTextColor
|
|
}
|
|
}
|
|
}
|
|
// Product code UPC, EAN, JAN, SKU, ISBN (for books) // https://www.simplybarcodes.com/barcode_types.html
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
|
|
Row {
|
|
spacing: 10
|
|
Text {
|
|
text: "Product code"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
Text {
|
|
text: "(OPTIONAL)"
|
|
color: productDialog.optTextColor
|
|
font.bold: true
|
|
font.pointSize: 8
|
|
anchors.verticalCenter: parent.children[0].verticalCenter
|
|
}
|
|
}
|
|
|
|
Row {
|
|
spacing: 5
|
|
TextField {
|
|
id: productCodeField
|
|
width: 500 - parent.children[1].width - parent.spacing; height: 50
|
|
placeholderText: qsTr("Enter product code")
|
|
color: productDialog.inputTextColor
|
|
selectByMouse: true
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
}
|
|
NeroshopComponents.ComboBox {
|
|
id: productCodeType
|
|
height: parent.children[0].height//Layout.preferredWidth: 100; Layout.preferredHeight: parent.children[0].height
|
|
model: ["EAN", "ISBN", "JAN", "SKU", "UPC"] // default is UPC (each code will be validated before product is listed)
|
|
Component.onCompleted: currentIndex = find("UPC")
|
|
radius: productDialog.inputRadius
|
|
color: productDialog.inputBaseColor
|
|
textColor: productDialog.inputTextColor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Product categories
|
|
Item {
|
|
//Layout.row:
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
function getCategoryStringList() {
|
|
let categoryStringList = []
|
|
let categories = Backend.getCategoryList(true)
|
|
for(let i = 0; i < categories.length; i++) {
|
|
categoryStringList[i] = categories[i].name//console.log(parent.parent.parent.categoryStringList[i])//console.log(categories[i].name)
|
|
}
|
|
return categoryStringList;
|
|
}
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Category"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
|
|
Row {
|
|
spacing: 5
|
|
NeroshopComponents.ComboBox {
|
|
id: productCategoryBox
|
|
width: addSubCategoryButton.visible ? (500 - addSubCategoryButton.width - parent.spacing) : 500; height: 50
|
|
model: parent.parent.parent.getCategoryStringList()
|
|
Component.onCompleted: {
|
|
currentIndex = find("Miscellaneous")
|
|
}
|
|
function reset() {
|
|
let subcategories = Backend.getSubCategoryList(Backend.getCategoryIdByName(productCategoryBox.currentText), true)
|
|
addSubCategoryButton.visible = (subcategories.length > 0)
|
|
subCategoryRepeater.model = 0 // reset
|
|
}
|
|
onActivated: {
|
|
productCategoryBox.reset()
|
|
}
|
|
radius: productDialog.inputRadius
|
|
color: productDialog.inputBaseColor
|
|
textColor: productDialog.inputTextColor
|
|
}
|
|
Button {
|
|
id: addSubCategoryButton
|
|
width: 50; height: 50
|
|
text: qsTr("+")
|
|
visible: Backend.hasSubCategory(Backend.getCategoryIdByName(productCategoryBox.currentText))
|
|
background: Rectangle {
|
|
color: parent.hovered ? "#698b22" : "#506a1a"//"#605185"
|
|
radius: productDialog.inputRadius
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: "#ffffff"
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
onClicked: {
|
|
let subcategories = Backend.getSubCategoryList(Backend.getCategoryIdByName(productCategoryBox.currentText), true)
|
|
let max_subcategories = Math.min(2, subcategories.length)
|
|
if(subCategoryRepeater.count == max_subcategories) {
|
|
console.log("Cannot add no more than " + max_subcategories + " subcategories")
|
|
return
|
|
}
|
|
subCategoryRepeater.model = subCategoryRepeater.model + 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Subcategories (will be determined based on selected categories)
|
|
Item {
|
|
id: subCategoryItem
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
visible: (subCategoryRepeater.count > 0)//Backend.hasSubCategory(Backend.getCategoryIdByName(productCategoryBox.currentText))
|
|
|
|
function getSubCategoryStringList() {
|
|
let subCategoryStringList = []
|
|
let subcategories = Backend.getSubCategoryList(Backend.getCategoryIdByName(productCategoryBox.currentText), true)
|
|
for(let i = 0; i < subcategories.length; i++) {
|
|
subCategoryStringList[i] = subcategories[i].name//console.log(parent.parent.parent.categoryStringList[i])//console.log(categories[i].name)
|
|
}
|
|
return subCategoryStringList;
|
|
}
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Subcategory"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
|
|
Repeater {
|
|
id: subCategoryRepeater
|
|
model: 0
|
|
delegate: Row {
|
|
spacing: 5
|
|
NeroshopComponents.ComboBox {
|
|
id: productSubCategoryBox
|
|
width: removeSubCategoryButton.visible ? (500 - removeSubCategoryButton.width - parent.spacing) : 500; height: 50
|
|
model: parent.parent.parent.getSubCategoryStringList()
|
|
currentIndex: 0
|
|
radius: productDialog.inputRadius
|
|
color: productDialog.inputBaseColor
|
|
textColor: productDialog.inputTextColor
|
|
}
|
|
Button {
|
|
id: removeSubCategoryButton
|
|
width: 50; height: 50
|
|
text: qsTr("x")
|
|
background: Rectangle {
|
|
color: parent.hovered ? "#b22222" : "#921c1c"
|
|
radius: productDialog.inputRadius
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: "#ffffff"
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
onClicked: {
|
|
subCategoryRepeater.model = subCategoryRepeater.model - 1
|
|
}
|
|
}
|
|
} // Row
|
|
}
|
|
}
|
|
}
|
|
// Weight
|
|
Item {
|
|
//Layout.row:
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Row {
|
|
spacing: 10
|
|
Text {
|
|
text: "Weight"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
Text {
|
|
text: "(OPTIONAL)"
|
|
color: productDialog.optTextColor
|
|
font.bold: true
|
|
font.pointSize: 8
|
|
anchors.verticalCenter: parent.children[0].verticalCenter
|
|
}
|
|
}
|
|
|
|
Row {
|
|
spacing: 5
|
|
TextField {
|
|
id: productWeightField
|
|
width: 500 - parent.children[1].width - parent.spacing; height: 50
|
|
placeholderText: qsTr("Enter weight")
|
|
color: productDialog.inputTextColor
|
|
selectByMouse: true
|
|
validator: RegExpValidator{ regExp: new RegExp("^-?[0-9]+(\\.[0-9]{1," + 8 + "})?$") }
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
}
|
|
NeroshopComponents.ComboBox {
|
|
id: weightMeasurementUnit
|
|
height: parent.children[0].height
|
|
model: ["kg", "lb"] // default is kg (every unit of measurement will be converted to kg)
|
|
Component.onCompleted: currentIndex = find("kg")
|
|
radius: productDialog.inputRadius
|
|
color: productDialog.inputBaseColor
|
|
textColor: productDialog.inputTextColor
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Size
|
|
/*Item {
|
|
//Layout.row:
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Row {
|
|
spacing: 10
|
|
Text {
|
|
text: "Size"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
Text {
|
|
text: "(OPTIONAL)"
|
|
color: productDialog.optTextColor
|
|
font.bold: true
|
|
font.pointSize: 8
|
|
anchors.verticalCenter: parent.children[0].verticalCenter
|
|
}
|
|
}
|
|
|
|
Row {
|
|
spacing: 5
|
|
TextField {
|
|
id: productSizeField
|
|
width: 500; height: 50
|
|
placeholderText: qsTr("Enter size")
|
|
color: productDialog.inputTextColor
|
|
selectByMouse: true
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
}
|
|
//ComboBox
|
|
}
|
|
}
|
|
}*/
|
|
// Variations/Attributes (i.e. Color, Size, Type, Model, etc. options to choose from - optional)
|
|
// Product location (ship to and ship from)
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
property var countriesModel: ["Afghanistan", "Albania", "Algeria", "Andorra", "Angola", "Antigua & Deps", "Argentina", "Armenia", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia Herzegovina", "Botswana", "Brazil", "Brunei", "Bulgaria", "Burkina", "Burundi", "Cambodia", "Cameroon", "Canada", "Cape Verde", "Central African Rep", "Chad", "Chile", "China", "Colombia", "Comoros", "Congo", "Congo (Democratic Rep)", "Costa Rica", "Croatia", "Cuba", "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "East Timor", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Eswatini", "Ethiopia", "Fiji", "Finland", "France", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Greece", "Grenada", "Guatemala", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Honduras", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland (Republic)", "Israel", "Italy", "Ivory Coast", "Jamaica", "Japan", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "Korea North", "Korea South", "Kosovo", "Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macedonia", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Mauritania", "Mauritius", "Mexico", "Micronesia", "Moldova", "Monaco", "Mongolia", "Montenegro", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Poland", "Portugal", "Qatar", "Romania", "Russian Federation", "Rwanda", "St Kitts & Nevis", "St Lucia", "Saint Vincent & the Grenadines", "Samoa", "San Marino", "Sao Tome & Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Togo", "Tonga", "Trinidad & Tobago", "Tunisia", "Turkey", "Turkmenistan", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Unspecified", "Uruguay", "Uzbekistan", "Vanuatu", "Vatican City", "Venezuela", "Vietnam", "Yemen", "Zambia", "Zimbabwe", "Worldwide",]
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Row {
|
|
spacing: 10
|
|
Text {
|
|
text: "Location"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
Text {
|
|
text: qsTr(FontAwesome.questionCircle)
|
|
color: productDialog.optTextColor
|
|
font.bold: true
|
|
anchors.verticalCenter: parent.children[0].verticalCenter
|
|
property bool hovered: false
|
|
NeroshopComponents.Hint {
|
|
x: parent.width + 10; y: ((parent.height - height) / 2) - 3
|
|
visible: parent.hovered
|
|
height: contentHeight + 20; width: contentWidth + 20
|
|
text: qsTr("Ships from")
|
|
pointer.visible: false;
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onEntered: parent.hovered = true
|
|
onExited: parent.hovered = false
|
|
}
|
|
}
|
|
}
|
|
|
|
NeroshopComponents.ComboBox {
|
|
id: productLocationBox
|
|
width: 500; height: 50
|
|
model: parent.parent.countriesModel
|
|
Component.onCompleted: {
|
|
contentItem.selectByMouse = true
|
|
currentIndex = find("Unspecified")//find("Worldwide")
|
|
}
|
|
editable: true
|
|
onAccepted: {
|
|
if(find(editText) === -1)
|
|
model.append({text: editText})
|
|
}
|
|
radius: productDialog.inputRadius
|
|
color: productDialog.inputBaseColor
|
|
textColor: productDialog.inputTextColor
|
|
}
|
|
}
|
|
}
|
|
// todo: Shipping details
|
|
//Product description and bullet points
|
|
Item {
|
|
//Layout.row:
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Description"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
|
|
ScrollView {
|
|
width: 500; height: 250
|
|
TextArea {
|
|
id: productDescriptionEdit
|
|
placeholderText: qsTr("Enter description")
|
|
wrapMode: Text.Wrap //Text.Wrap moves text to the newline when it reaches the width//Text.WordWrap does not move text to the newline but instead it only shows the scrollbar
|
|
selectByMouse: true
|
|
color: productDialog.inputTextColor
|
|
|
|
background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Product tags
|
|
Item {
|
|
//Layout.row:
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.preferredWidth: childrenRect.width
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Tags"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
|
|
NeroshopComponents.TagField {
|
|
id: productTagsField
|
|
width: 500
|
|
|
|
textField.color: productDialog.inputTextColor
|
|
textField.selectByMouse: true
|
|
textField.background: Rectangle {
|
|
color: "transparent"
|
|
border.color: productDialog.inputBorderColor
|
|
border.width: parent.activeFocus ? 2 : 1
|
|
radius: productDialog.inputRadius
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//Product images
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter//Qt.AlignRight
|
|
Layout.preferredWidth: childrenRect.width//Layout.fillWidth: true
|
|
Layout.preferredHeight: childrenRect.height
|
|
|
|
Column {
|
|
spacing: productDialog.titleSpacing
|
|
Text {
|
|
text: "Upload image(s)"
|
|
color: productDialog.palette.text
|
|
font.bold: true
|
|
}
|
|
|
|
Flickable {
|
|
width: 500; height: 210 + ScrollBar.horizontal.height// same height as delegateRect + scrollbar height
|
|
contentWidth: (210 * productImageRepeater.count) + (5 * (productImageRepeater.count - 1))//<- 5 is the Flow.spacing//; contentHeight: 210//<- contentHeight is not needed unless a newline is supported
|
|
clip: true
|
|
ScrollBar.horizontal: ScrollBar {
|
|
policy: ScrollBar.AlwaysOn//AsNeeded
|
|
}
|
|
Flow {
|
|
width: parent.contentWidth; height: parent.height//anchors.fill: parent// Note: Flow width must be large enough to fit all items horizontally so that there won't be a need to move an item to a newline
|
|
spacing: 5
|
|
Repeater {
|
|
id: productImageRepeater
|
|
model: 6
|
|
property int lastImageIndex: -1
|
|
delegate: Rectangle {//Item {
|
|
width: 210; height: 210
|
|
color: "transparent"
|
|
//border.color: "blue"
|
|
|
|
Rectangle {
|
|
border.color: productDialog.palette.text
|
|
anchors.top: parent.top
|
|
anchors.topMargin: 5
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
width: parent.width; height: parent.height - 50
|
|
color: "transparent"//"#d3d3d3"//
|
|
Image {
|
|
anchors.centerIn: parent // This is not necessary since the image is the same size as its parent rect but I'll keep it there just to be sure the image is centered
|
|
width: parent.width; height: parent.height
|
|
fillMode: Image.PreserveAspectFit // scale to fit
|
|
////source: productImageFileDialog.file
|
|
mipmap: true // produces better image quality that is not pixely but smooth :D
|
|
//asynchronous: true // won't block the app
|
|
onStatusChanged: {
|
|
if(this.status == Image.Ready) {
|
|
console.log(source + ' has been loaded')
|
|
// TODO: Upload image to the database
|
|
productImageRepeater.lastImageIndex = index // Update the lastImageIndex
|
|
}
|
|
if(this.status == Image.Loading) console.log("Loading image " + source + " (" + (progress * 100) + "%)")
|
|
if(this.status == Image.Error) console.log("An error occurred while loading the image")
|
|
//if(this.status == Image.Null) console.log("No image has been set" + parent.parent.index)
|
|
}
|
|
}
|
|
// Position the close button
|
|
Button {
|
|
id: removeImageButton
|
|
anchors.right: parent.right
|
|
anchors.top: parent.top
|
|
anchors.margins: 8
|
|
|
|
width: 20; height: 20//32
|
|
text: qsTr(FontAwesome.xmark)
|
|
hoverEnabled: true
|
|
visible: (parent.children[0].status === Image.Ready) && (index === productImageRepeater.lastImageIndex)
|
|
|
|
contentItem: Text {
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
text: removeImageButton.text
|
|
color: removeImageButton.hovered ? "#ffffff" : "#000000"
|
|
font.bold: true
|
|
font.family: FontAwesome.fontFamily
|
|
}
|
|
|
|
background: Rectangle {
|
|
width: parent.width
|
|
height: parent.height
|
|
radius: 5//50
|
|
color: removeImageButton.hovered ? "firebrick" : "transparent"
|
|
opacity: 0.7
|
|
}
|
|
|
|
onClicked: {
|
|
parent.children[0].source = ""
|
|
productImageRepeater.lastImageIndex = productImageRepeater.lastImageIndex - 1
|
|
}
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onPressed: mouse.accepted = false
|
|
cursorShape: Qt.PointingHandCursor
|
|
}
|
|
}
|
|
}
|
|
|
|
Button {
|
|
id: chooseFileButton
|
|
width: 150; height: contentItem.contentHeight + 12
|
|
text: qsTr("Choose File")// %1%").arg(parent.children[0].children[0].progress * 100)
|
|
anchors.bottom: parent.bottom
|
|
anchors.bottomMargin: 5
|
|
anchors.horizontalCenter: parent.children[0].horizontalCenter
|
|
background: Rectangle {
|
|
color: parent.hovered ? "lightslategray" : "slategray"
|
|
radius: productDialog.inputRadius
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: parent.hovered ? "#d9d9d9" : "#262626"
|
|
verticalAlignment: Text.AlignVCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
}
|
|
onClicked: {
|
|
if(index != 0) {
|
|
let prevProductImage = productImageRepeater.itemAt(index - 1).children[0].children[0] // get image at previous index
|
|
if(prevProductImage.status == Image.Null) { console.log("Images must be loaded in order from left to right"); return; } // If image at the previous index has not been loaded then exit this function
|
|
}
|
|
productImageFileDialog.open()
|
|
}
|
|
}
|
|
FileDialog {
|
|
id: productImageFileDialog
|
|
fileMode: FileDialog.OpenFile
|
|
folder: (isWindows) ? StandardPaths.writableLocation(StandardPaths.DocumentsLocation) + "/neroshop" : StandardPaths.writableLocation(StandardPaths.HomeLocation) + "/neroshop"
|
|
nameFilters: ["Image files (*.bmp *.gif *.jpeg *.jpg *.png *.tif *.tiff)"]
|
|
onAccepted: productImageRepeater.itemAt(index).children[0].children[0].source = currentFile
|
|
}
|
|
}
|
|
}
|
|
} // flow
|
|
}
|
|
}
|
|
}
|
|
// ListItem to "listings" table
|
|
Item {
|
|
Layout.alignment: Qt.AlignHCenter//Qt.AlignRight
|
|
Layout.preferredWidth: childrenRect.width//Layout.fillWidth: true
|
|
Layout.preferredHeight: childrenRect.height
|
|
Row {
|
|
spacing: 5
|
|
// cancelButton
|
|
Button {
|
|
id: cancelButton
|
|
width: 166.666666667 - parent.spacing; height: contentItem.contentHeight + 30
|
|
hoverEnabled: true
|
|
text: qsTr("Cancel")
|
|
background: Rectangle {
|
|
color: parent.hovered ? "#b22222" : "#921c1c"
|
|
radius: productDialog.inputRadius
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: "#ffffff"
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
onClicked: {
|
|
productDialog.close()
|
|
mainScrollView.ScrollBar.vertical.position = 0.0 // reset scrollbar
|
|
}
|
|
}
|
|
// listButton
|
|
Button {
|
|
id: listProductButton
|
|
width: 333.333333333; height: contentItem.contentHeight + 30
|
|
hoverEnabled: true
|
|
text: qsTr("Submit")
|
|
background: Rectangle {
|
|
color: parent.hovered ? "#698b22" : "#506a1a"
|
|
radius: productDialog.inputRadius
|
|
}
|
|
contentItem: Text {
|
|
text: parent.text
|
|
color: "#ffffff"
|
|
horizontalAlignment: Text.AlignHCenter
|
|
verticalAlignment: Text.AlignVCenter
|
|
}
|
|
onClicked: {
|
|
// Check input fields to see if entered info is valid
|
|
// ...
|
|
//---------------------------------------
|
|
if(productNameField.text.length < 3) {
|
|
messageBox.text = qsTr("Item name is too short")
|
|
messageBox.open()
|
|
return; // exit function
|
|
}
|
|
//---------------------------------------
|
|
let subcategory_ids = []//let subcategories = []
|
|
for (let i = 0; i < subCategoryRepeater.count; i++) {
|
|
subcategory_ids.push(Backend.getSubCategoryIdByName(subCategoryRepeater.itemAt(i).children[0].currentText))//subcategories.push(subCategoryRepeater.itemAt(i).children[0].currentText)//console.log("Added subcategory: ", subcategories[i])
|
|
}
|
|
// Product subcategories cannot be duplicated
|
|
if(subCategoryRepeater.count >= 2) {
|
|
if((new Set(subcategory_ids)).size !== subcategory_ids.length) {
|
|
messageBox.text = "Item cannot have duplicate subcategories"
|
|
messageBox.open()
|
|
return; // exit function
|
|
}
|
|
}
|
|
//---------------------------------------
|
|
// Product attributes (each attribute object represents a variant of the product)
|
|
let attributes = [];
|
|
let attribute_object = {};
|
|
if(productWeightField.text.length > 0 && Number(productWeightField.text) > 0.00) {
|
|
if(weightMeasurementUnit.currentText !== "kg") {
|
|
console.log("weight is in " + weightMeasurementUnit.currentText + ". Converting to kg ...")
|
|
attribute_object.weight = Backend.weightToKg(Number(productWeightField.text), weightMeasurementUnit.currentText)
|
|
} else {
|
|
attribute_object.weight = Number(productWeightField.text)
|
|
}
|
|
}
|
|
// Add attribute obj to list as long as its filled with properties
|
|
if (Object.keys(attribute_object).length > 0) {
|
|
attributes.push(attribute_object)
|
|
}
|
|
//---------------------------------------
|
|
// Ship from location must be valid
|
|
if(productLocationBox.model.indexOf(productLocationBox.contentItem.text) === -1) {
|
|
messageBox.text = "Invalid location entered"
|
|
messageBox.open()
|
|
return; // exit function
|
|
}
|
|
//---------------------------------------
|
|
// Product must have a minimum of 1 image
|
|
let productThumbnail = productImageRepeater.itemAt(0).children[0].children[0]
|
|
if(productThumbnail.status == Image.Null) {
|
|
messageBox.text = "Item must have at least 1 image"
|
|
messageBox.open()
|
|
return; // exit function
|
|
}
|
|
//---------------------------------------
|
|
// TODO: add weight and product_code to attributes list instead
|
|
// Attributes will be in JSON format
|
|
// Todo: check whether its a product or service
|
|
let productImages = []
|
|
for(let i = 0; i < productImageRepeater.count; i++) {
|
|
let productImage = productImageRepeater.itemAt(i).children[0].children[0]
|
|
if(productImage.status == Image.Ready) { // If image loaded
|
|
console.log("uploading " + Backend.urlToLocalFile(productImage.source) + " to the database")
|
|
let image = Backend.uploadProductImage(Backend.urlToLocalFile(productImage.source), i)
|
|
productImages.push(image);
|
|
}
|
|
}
|
|
// List product
|
|
let listing_key = User.listProduct(
|
|
productNameField.text,
|
|
productDescriptionEdit.text,
|
|
attributes,
|
|
(productCodeField.text.length >= 6) ? productCodeType.currentText.toLowerCase() + ":" + productCodeField.text : "",
|
|
Backend.getCategoryIdByName(productCategoryBox.currentText),
|
|
(subCategoryRepeater.count > 0) ? subcategory_ids : [], // subcategoryIds
|
|
productTagsField.tags(),
|
|
productImages,
|
|
|
|
productQuantityField.text,
|
|
productPriceField.text,
|
|
selectedCurrencyText.text,
|
|
productConditionBox.currentText,
|
|
productLocationBox.currentText,
|
|
(quantityPerOrderField.text.length > 0) ? Number(quantityPerOrderField.text) : 0
|
|
)
|
|
// Save product thumbnail
|
|
Backend.saveProductThumbnail(productImages[0].source, listing_key)
|
|
// Save product image(s) to datastore folder
|
|
for (let i = 0; i < productImages.length; i++) {
|
|
Backend.saveProductImage(productImages[i].source, listing_key)
|
|
}
|
|
// Clear input fields after listing product
|
|
productNameField.text = ""
|
|
productPriceField.text = ""
|
|
productQuantityField.text = "1"
|
|
productConditionBox.currentIndex = productConditionBox.find("New")
|
|
productCodeField.text = ""
|
|
productCategoryBox.currentIndex = productCategoryBox.find("Miscellaneous")
|
|
productCategoryBox.reset() // resets subcategories
|
|
productWeightField.text = ""
|
|
productLocationBox.currentIndex = productLocationBox.find("Unspecified")//find("Worldwide")
|
|
productDescriptionEdit.text = ""
|
|
productTagsField.clearTags()
|
|
quantityPerOrderField.text = ""
|
|
// Clear upload images as well
|
|
for(let i = 0; i < productImageRepeater.count; i++) {
|
|
let productImage = productImageRepeater.itemAt(i).children[0].children[0]
|
|
productImage.source = ""
|
|
}
|
|
// close productDialog
|
|
productDialog.close()
|
|
// reset scrollbar
|
|
mainScrollView.ScrollBar.vertical.position = 0.0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|