Custom shipping cost (#86)
* feat: simple UI for shipping zone per product * feat: add empty cost * fix: backwards compatible zones * feat: finish UI for product shipping cost * fix: some ui issues * feat: add per product shipping cost * feat: show receipt for product * fix: publish per product shipping cost
This commit is contained in:
parent
2dc5c5479f
commit
5c83bf8972
6 changed files with 154 additions and 82 deletions
|
|
@ -102,20 +102,11 @@
|
|||
</q-td>
|
||||
|
||||
<q-td key="id" :props="props"> {{props.row.id}} </q-td>
|
||||
<q-td key="name" :props="props"> {{props.row.name}} </q-td>
|
||||
<q-td key="name" :props="props"> {{shortLabel(props.row.name)}} </q-td>
|
||||
<q-td key="price" :props="props"> {{props.row.price}} </q-td>
|
||||
<q-td key="quantity" :props="props">
|
||||
{{props.row.quantity}}
|
||||
</q-td>
|
||||
|
||||
<q-td key="categories" :props="props">
|
||||
<div>
|
||||
{{props.row.categories.filter(c => c).join(', ')}}
|
||||
</div>
|
||||
</q-td>
|
||||
<q-td key="description" :props="props">
|
||||
{{props.row.config.description}}
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
|
|
@ -131,35 +122,66 @@
|
|||
</q-tab-panel>
|
||||
</q-tab-panels>
|
||||
<q-dialog v-model="productDialog.showDialog" position="top">
|
||||
<q-card v-if="stall" class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||
<q-card v-if="stall && productDialog.data" class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||
<q-form @submit="sendProductFormData" class="q-gutter-md">
|
||||
<q-input filled dense v-model.trim="productDialog.data.name" label="Name"></q-input>
|
||||
|
||||
<q-input filled dense v-model.trim="productDialog.data.config.description" label="Description"></q-input>
|
||||
<q-select filled multiple dense emit-value v-model.trim="productDialog.data.categories" use-input use-chips
|
||||
multiple hide-dropdown-icon input-debounce="0" new-value-mode="add-unique"
|
||||
label="Categories (Hit Enter to add)" placeholder="crafts,robots,etc"></q-select>
|
||||
|
||||
<q-input filled dense v-model.trim="productDialog.data.image" @keydown.enter="addProductImage" type="url"
|
||||
label="Image URL">
|
||||
<q-btn @click="addProductImage" dense flat icon="add"></q-btn></q-input>
|
||||
|
||||
<q-chip v-for="imageUrl in productDialog.data.images" :key="imageUrl" removable
|
||||
@remove="removeProductImage(imageUrl)" color="primary" text-color="white">
|
||||
<span v-text="imageUrl.split('/').pop()"></span>
|
||||
</q-chip>
|
||||
<q-input filled dense v-model.number="productDialog.data.price" type="number"
|
||||
:label="'Price (' + stall.currency + ') *'" :step="stall.currency != 'sat' ? '0.01' : '1'"
|
||||
:mask="stall.currency != 'sat' ? '#.##' : '#'" fill-mask="0" reverse-fill-mask></q-input>
|
||||
<q-input filled dense v-model.number="productDialog.data.quantity" type="number" label="Quantity"></q-input>
|
||||
<div class="row q-mb-sm">
|
||||
<div class="col">
|
||||
<q-input filled dense v-model.number="productDialog.data.price" type="number"
|
||||
:label="'Price (' + stall.currency + ') *'" :step="stall.currency != 'sat' ? '0.01' : '1'"
|
||||
:mask="stall.currency != 'sat' ? '#.##' : '#'" fill-mask="0" reverse-fill-mask></q-input>
|
||||
</div>
|
||||
<div class="col q-ml-md">
|
||||
<q-input filled dense v-model.number="productDialog.data.quantity" type="number" label="Quantity"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<q-expansion-item group="advanced" icon="settings" label="Advanced options">
|
||||
<q-expansion-item group="advanced" label="Categories"
|
||||
caption="Add tags to producsts, make them easy to search.">
|
||||
<div class="q-pl-sm q-pt-sm">
|
||||
<q-select filled multiple dense emit-value v-model.trim="productDialog.data.categories" use-input use-chips
|
||||
multiple hide-dropdown-icon input-debounce="0" new-value-mode="add-unique"
|
||||
label="Categories (Hit Enter to add)" placeholder="crafts,robots,etc"></q-select>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
|
||||
<q-expansion-item group="advanced" label="Images" caption="Add images for product.">
|
||||
<div class="q-pl-sm q-pt-sm">
|
||||
<q-input filled dense v-model.trim="productDialog.data.image" @keydown.enter="addProductImage" type="url"
|
||||
label="Image URL">
|
||||
<q-btn @click="addProductImage" dense flat icon="add"></q-btn></q-input>
|
||||
|
||||
<q-chip v-for="imageUrl in productDialog.data.images" :key="imageUrl" removable
|
||||
@remove="removeProductImage(imageUrl)" color="primary" text-color="white">
|
||||
<span v-text="imageUrl.split('/').pop()"></span>
|
||||
</q-chip>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
|
||||
|
||||
<q-expansion-item group="advanced" label="Custom Shipping Cost"
|
||||
caption="Configure custom shipping costs for this product">
|
||||
<div v-for="zone of productDialog.data.config.shipping" class="row q-mb-sm q-ml-lg q-mt-sm">
|
||||
<div class="col">
|
||||
<span v-text="zone.name"></span>
|
||||
</div>
|
||||
<div class="col q-pr-md">
|
||||
<q-input v-model="zone.cost" filled dense type="number" label="Extra cost">
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item group="advanced" label="Autoreply" caption="Autoreply when paid">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="row q-mb-sm">
|
||||
<div class="col">
|
||||
<q-checkbox v-model="productDialog.data.config.use_autoreply" dense label="Autoreply when paid" />
|
||||
<q-checkbox v-model="productDialog.data.config.use_autoreply" dense
|
||||
label="Send a direct message when paid" class="q-ml-sm" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -23,19 +23,7 @@ async function stallDetails(path) {
|
|||
showDialog: false,
|
||||
showRestore: false,
|
||||
url: true,
|
||||
data: {
|
||||
id: null,
|
||||
name: '',
|
||||
categories: [],
|
||||
images: [],
|
||||
image: null,
|
||||
price: 0,
|
||||
|
||||
quantity: 0,
|
||||
config: {
|
||||
description: ''
|
||||
}
|
||||
}
|
||||
data: null
|
||||
},
|
||||
productsFilter: '',
|
||||
productsTable: {
|
||||
|
|
@ -76,18 +64,6 @@ async function stallDetails(path) {
|
|||
align: 'left',
|
||||
label: 'Quantity',
|
||||
field: 'quantity'
|
||||
},
|
||||
{
|
||||
name: 'categories',
|
||||
align: 'left',
|
||||
label: 'Categories',
|
||||
field: 'categories'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
align: 'left',
|
||||
label: 'Description',
|
||||
field: 'description'
|
||||
}
|
||||
],
|
||||
pagination: {
|
||||
|
|
@ -112,6 +88,24 @@ async function stallDetails(path) {
|
|||
)
|
||||
return stall
|
||||
},
|
||||
newEmtpyProductData: function() {
|
||||
return {
|
||||
id: null,
|
||||
name: '',
|
||||
categories: [],
|
||||
images: [],
|
||||
image: null,
|
||||
price: 0,
|
||||
|
||||
quantity: 0,
|
||||
config: {
|
||||
description: '',
|
||||
use_autoreply: false,
|
||||
autoreply_message: '',
|
||||
shipping: (this.stall.shipping_zones || []).map(z => ({id: z.id, name: z.name, cost: 0}))
|
||||
}
|
||||
}
|
||||
},
|
||||
getStall: async function () {
|
||||
try {
|
||||
const { data } = await LNbits.api.request(
|
||||
|
|
@ -202,7 +196,7 @@ async function stallDetails(path) {
|
|||
}
|
||||
},
|
||||
sendProductFormData: function () {
|
||||
var data = {
|
||||
const data = {
|
||||
stall_id: this.stall.id,
|
||||
id: this.productDialog.data.id,
|
||||
name: this.productDialog.data.name,
|
||||
|
|
@ -265,7 +259,14 @@ async function stallDetails(path) {
|
|||
}
|
||||
},
|
||||
editProduct: async function (product) {
|
||||
const emptyShipping = this.newEmtpyProductData().config.shipping
|
||||
this.productDialog.data = { ...product }
|
||||
this.productDialog.data.config.shipping = emptyShipping.map(shippingZone => {
|
||||
const existingShippingCost = (product.config.shipping || []).find(ps => ps.id === shippingZone.id)
|
||||
shippingZone.cost = existingShippingCost?.cost || 0
|
||||
return shippingZone
|
||||
})
|
||||
|
||||
this.productDialog.showDialog = true
|
||||
},
|
||||
deleteProduct: async function (productId) {
|
||||
|
|
@ -293,19 +294,7 @@ async function stallDetails(path) {
|
|||
})
|
||||
},
|
||||
showNewProductDialog: async function (data) {
|
||||
this.productDialog.data = data || {
|
||||
id: null,
|
||||
name: '',
|
||||
description: '',
|
||||
categories: [],
|
||||
image: null,
|
||||
images: [],
|
||||
price: 0,
|
||||
quantity: 0,
|
||||
config: {
|
||||
description: ''
|
||||
}
|
||||
}
|
||||
this.productDialog.data = data || this.newEmtpyProductData()
|
||||
this.productDialog.showDialog = true
|
||||
},
|
||||
openSelectPendingProductDialog: async function () {
|
||||
|
|
@ -324,11 +313,16 @@ async function stallDetails(path) {
|
|||
},
|
||||
customerSelectedForOrder: function (customerPubkey) {
|
||||
this.$emit('customer-selected-for-order', customerPubkey)
|
||||
},
|
||||
shortLabel(value = ''){
|
||||
if (value.length <= 44) return value
|
||||
return value.substring(0, 40) + '...'
|
||||
}
|
||||
},
|
||||
created: async function () {
|
||||
await this.getStall()
|
||||
this.products = await this.getProducts()
|
||||
this.productDialog.data = this.newEmtpyProductData()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,15 +34,14 @@
|
|||
:icon="props.row.expanded? 'remove' : 'add'" />
|
||||
</q-td>
|
||||
|
||||
<q-td key="id" :props="props"> {{props.row.name}} </q-td>
|
||||
<q-td key="id" :props="props"> {{shortLabel(props.row.name)}} </q-td>
|
||||
<q-td key="currency" :props="props"> {{props.row.currency}} </q-td>
|
||||
<q-td key="description" :props="props">
|
||||
{{props.row.config.description}}
|
||||
{{shortLabel(props.row.config.description)}}
|
||||
</q-td>
|
||||
<q-td key="shippingZones" :props="props">
|
||||
<div>
|
||||
{{props.row.shipping_zones.filter(z => !!z.name).map(z =>
|
||||
z.name).join(', ')}}
|
||||
{{shortLabel(props.row.shipping_zones.filter(z => !!z.name).map(z => z.name).join(', '))}}
|
||||
</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
|
|
|
|||
|
|
@ -251,6 +251,10 @@ async function stallList(path) {
|
|||
},
|
||||
customerSelectedForOrder: function (customerPubkey) {
|
||||
this.$emit('customer-selected-for-order', customerPubkey)
|
||||
},
|
||||
shortLabel(value = ''){
|
||||
if (value.length <= 64) return value
|
||||
return value.substring(0, 60) + '...'
|
||||
}
|
||||
},
|
||||
created: async function () {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue