summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam T. Carpenter <atc@53hor.net>2020-11-12 16:27:13 -0500
committerAdam T. Carpenter <atc@53hor.net>2020-11-12 16:27:13 -0500
commit9b77f9ec2c00b48c551f65b2e9d7a087004de4c0 (patch)
treeb7eb96fc4a2c7baffcb4acfc93c572ab079f11a2
parent7381a7033231e6454a37fd64b1f3de4e8d59355f (diff)
downloadtheglassyladies-9b77f9ec2c00b48c551f65b2e9d7a087004de4c0.tar.xz
theglassyladies-9b77f9ec2c00b48c551f65b2e9d7a087004de4c0.zip
Noice. Product creation and updating is totally functional.
-rw-r--r--iridescence/src/components/ProductSearch.vue2
-rw-r--r--iridescence/src/components/admin/NewProduct.vue17
-rw-r--r--iridescence/src/components/admin/ProductEditCard.vue229
-rw-r--r--iridescence/src/components/admin/ProductEditList.vue6
-rw-r--r--iridescence/src/models/product.js4
-rw-r--r--iridescence/src/models/product_diff.js43
-rw-r--r--iridescence/src/store/index.js28
7 files changed, 167 insertions, 162 deletions
diff --git a/iridescence/src/components/ProductSearch.vue b/iridescence/src/components/ProductSearch.vue
index d452e83..60da209 100644
--- a/iridescence/src/components/ProductSearch.vue
+++ b/iridescence/src/components/ProductSearch.vue
@@ -40,7 +40,7 @@ export default {
},
computed: {
noResults() {
- return !this.$store.getters.products.length;
+ return !this.$store.getters.products.length && !this.$store.getters.busy;
}
},
methods: {
diff --git a/iridescence/src/components/admin/NewProduct.vue b/iridescence/src/components/admin/NewProduct.vue
index 48eb165..cd8a93e 100644
--- a/iridescence/src/components/admin/NewProduct.vue
+++ b/iridescence/src/components/admin/NewProduct.vue
@@ -17,7 +17,7 @@
<button class="delete" @click="toggleModal"></button>
</header>
<section class="modal-card-body">
- <ProductEditCard v-bind:parent-product="template"></ProductEditCard>
+ <ProductEditCard v-bind:index="-1"></ProductEditCard>
</section>
<footer class="modal-card-foot"></footer>
</div>
@@ -27,7 +27,6 @@
</template>
<script>
-import Product from "@/models/product";
import ProductEditCard from "@/components/admin/ProductEditCard";
export default {
@@ -37,11 +36,19 @@ export default {
},
data: function() {
return {
- modalEnabled: false,
- template: new Product({}),
- addAnother: false
+ modalEnabled: false
};
},
+ computed: {
+ numProducts: function() {
+ return this.$store.getters.products.length;
+ }
+ },
+ watch: {
+ numProducts: function() {
+ this.modalEnabled = false;
+ }
+ },
methods: {
toggleModal() {
this.modalEnabled = !this.modalEnabled;
diff --git a/iridescence/src/components/admin/ProductEditCard.vue b/iridescence/src/components/admin/ProductEditCard.vue
index a8117ce..aced39c 100644
--- a/iridescence/src/components/admin/ProductEditCard.vue
+++ b/iridescence/src/components/admin/ProductEditCard.vue
@@ -2,17 +2,38 @@
<div id="productEditCard">
<div class="card">
<div class="card-header">
- <p class="card-header-title" v-if="currentProduct.id">
- {{ currentProduct.id }}: {{ currentProduct.name }}
+ <p class="card-header-title" v-if="old.id">
+ {{ old.id }}: {{ old.name }}
</p>
</div>
<div class="card-content">
<div class="field">
+ <div class="file has-name is-boxed is-fullwidth">
+ <label class="file-label">
+ <input
+ class="file-input"
+ type="file"
+ name="image"
+ accept=".jpg,.jpeg,.JPG,.JPEG"
+ @change="changePhotoSet"
+ />
+ <span class="file-cta">
+ <span class="file-label has-text-centered">
+ Upload a Photo
+ </span>
+ </span>
+ <span class="file-name">
+ {{ filename }}
+ </span>
+ </label>
+ </div>
+ </div>
+ <div class="field">
<input
class="input"
type="text"
placeholder="(product name)"
- v-model="newProduct.name"
+ v-model="name"
/>
</div>
<div class="field">
@@ -20,7 +41,7 @@
class="input"
type="text"
placeholder="(product category)"
- v-model="newProduct.category_path"
+ v-model="category"
/>
</div>
<div class="field has-addons">
@@ -34,7 +55,7 @@
class="input"
type="text"
placeholder="inventory (quantity)"
- v-model="newProductQuantity"
+ v-model="quantity"
/>
</p>
<div class="control">
@@ -62,148 +83,184 @@
class="input"
type="text"
placeholder="price"
- v-model="newProductPrice"
+ v-model="price"
/>
</p>
</div>
<div class="field">
- <div class="file has-name is-boxed is-fullwidth">
- <label class="file-label">
- <input
- class="file-input"
- type="file"
- name="image"
- accept=".jpg,.jpeg,.JPG,.JPEG"
- @change="changePhotoSet"
- />
- <span class="file-cta">
- <span class="file-label has-text-centered">
- ↑ Choose a picture…
- </span>
- </span>
- <span class="file-name">
- {{ filename }}
- </span>
- </label>
- </div>
- </div>
- <div class="field">
<textarea
class="textarea"
type="text"
placeholder="(product description)"
- v-model="newProduct.description"
+ v-model="description"
>
</textarea>
</div>
<div class="field">
<label class="checkbox">
- <input type="checkbox" v-model="newProduct.featured" />
+ <input type="checkbox" v-model="featured" />
Featured?
</label>
</div>
</div>
- <transition
- enter-active-class="animate__animated animate__fadeIn"
- leave-active-class="animate__animated animate__fadeOut"
- >
- <div
- class="card-footer"
- v-if="
- newProduct.isValidPost() || newProduct.isValidPatch(currentProduct)
- "
- >
- <div class="card-footer-item">
- <button class="button is-primary is-fullwidth" @click="saveProduct">
- Save
- </button>
- </div>
+ <div class="card-footer" v-if="isValidPost || isValidPatch">
+ <div class="card-footer-item">
+ <button class="button is-primary is-fullwidth" @click="saveProduct">
+ Save
+ </button>
</div>
- </transition>
+ </div>
</div>
</div>
</template>
<script>
-import Product from "@/models/product";
-import ProductDiff from "@/models/product_diff";
-
const dollarRe = /^\$?(\d+)\.(\d{2})/gm;
export default {
name: "ProductEditCard",
data: function() {
return {
- currentProduct: new Product(this.parentProduct),
- newProduct: new ProductDiff(this.currentProduct),
- filename: ""
+ filename: "",
+ diff: {}
};
},
props: {
- parentProduct: {
- type: Product,
+ index: {
+ type: Number,
required: true
}
},
+ created() {
+ this.diff = {
+ id: (this.old && this.old.id) || null,
+ name: null,
+ description: null,
+ cents: null,
+ quantity: (this.old && this.old.quantity) || 0,
+ category_path: null,
+ featured:
+ this.old && typeof this.old.featured === "boolean"
+ ? this.old.featured
+ : false,
+ photo_set: null
+ };
+ },
computed: {
- newProductQuantity: {
+ old: function() {
+ return this.$store.getters.products[this.index] || {};
+ },
+ id: function() {
+ return this.diff.id || this.old.id;
+ },
+ name: {
get: function() {
- return this.newProduct.quantity;
+ return this.diff.name || this.old.name;
},
- set: function(val) {
- if (!isNaN(val)) {
- this.newProduct.quantity = 1 * val;
- }
+ set: function(name) {
+ this.diff.name = name;
}
},
- newProductPrice: {
+ description: {
get: function() {
- return (this.newProduct.cents / 100).toFixed(2);
+ return this.diff.description || this.old.description;
},
- set: function(val) {
- const groups = dollarRe.exec(val);
+ set: function(description) {
+ this.diff.description = description;
+ }
+ },
+ category: {
+ get: function() {
+ return this.diff.category_path || this.old.category;
+ },
+ set: function(category) {
+ this.diff.category_path = category;
+ }
+ },
+ featured: {
+ get: function() {
+ return this.diff.featured;
+ },
+ set: function(featured) {
+ this.diff.featured = featured;
+ }
+ },
+ price: {
+ get: function() {
+ const cents = this.diff.cents || this.old.cents || 0;
+ return (cents / 100).toFixed(2);
+ },
+ set: function(price) {
+ const groups = dollarRe.exec(price);
if (groups && groups[1] && groups[2]) {
- this.newProduct.cents = 100 * groups[1] + 1 * groups[2];
+ this.diff.cents = 100 * groups[1] + 1 * groups[2];
}
}
+ },
+ quantity: {
+ get: function() {
+ return this.diff.quantity || this.old.quantity || 0;
+ },
+ set: function(quantity) {
+ if (Number.isFinite(quantity)) {
+ this.diff.quantity = quantity;
+ }
+ }
+ },
+ isValidPost: function() {
+ return (
+ !this.diff.id &&
+ this.diff.name &&
+ this.diff.description &&
+ Number.isFinite(this.diff.cents) &&
+ Number.isFinite(this.diff.quantity) &&
+ this.diff.photo_set &&
+ this.diff.category_path &&
+ typeof this.featured === "boolean"
+ );
+ },
+ isValidPatch: function() {
+ return (
+ this.diff.id &&
+ (this.diff.photo_set ||
+ (this.diff.name && this.diff.name != this.old.name) ||
+ (Number.isFinite(this.diff.cents) &&
+ this.diff.cents != this.old.cents) ||
+ (this.diff.category_path &&
+ this.diff.category_path != this.old.category) ||
+ (Number.isFinite(this.diff.quantity) &&
+ this.diff.quantity != this.old.quantity) ||
+ (this.diff.description &&
+ this.diff.description != this.old.description) ||
+ this.diff.featured != this.old.featured)
+ );
}
},
methods: {
+ incrementQuantity(by) {
+ if (this.quantity + by >= 0) {
+ this.diff.quantity += by;
+ }
+ },
saveProduct() {
- if (this.newProduct.id) {
+ if (this.id) {
// update existing
- const updatedProduct = this.$store.dispatch(
- "updateProduct",
- this.newProduct
- );
- if (updatedProduct) {
- this.currentProduct = updatedProduct;
- }
+ this.$store.dispatch("updateProduct", this.diff);
} else {
// new product
- const newProduct = this.$store.dispatch(
- "createProduct",
- this.newProduct
- );
- if (newProduct) {
- this.currentProduct = newProduct;
- }
- }
- },
- incrementQuantity(amount) {
- if (this.newProduct.quantity + amount >= 0) {
- this.newProduct.quantity += amount;
+ this.$store.dispatch("createProduct", this.diff);
}
+
+ this.diff.photo_set = null;
},
changePhotoSet(event) {
const file = event.target.files[0];
if (!file) {
return;
}
-
this.$store.dispatch("createPhotoSet", file).then(r => {
this.filename = file.name;
- this.newProduct.photo_set = r[0].id;
+ this.diff.photo_set = r[0].id;
});
}
}
diff --git a/iridescence/src/components/admin/ProductEditList.vue b/iridescence/src/components/admin/ProductEditList.vue
index 24a276e..0880d7c 100644
--- a/iridescence/src/components/admin/ProductEditList.vue
+++ b/iridescence/src/components/admin/ProductEditList.vue
@@ -3,10 +3,10 @@
<div class="columns is-multiline is-variable is-desktop">
<div
class="column is-one-third-desktop"
- v-for="product in products"
- :key="product.id"
+ v-for="(product, index) in products"
+ :key="index"
>
- <ProductEditCard v-bind:parent-product="product"></ProductEditCard>
+ <ProductEditCard v-bind:index="index"></ProductEditCard>
</div>
</div>
</div>
diff --git a/iridescence/src/models/product.js b/iridescence/src/models/product.js
index c408b79..ded5434 100644
--- a/iridescence/src/models/product.js
+++ b/iridescence/src/models/product.js
@@ -4,8 +4,8 @@ export default class Product {
this.id = json.id ? json.id : null;
this.name = json.name ? json.name : null;
this.description = json.description ? json.description : null;
- this.cents = json.cents ? json.cents : null;
- this.quantity = json.quantity ? json.quantity : null;
+ this.cents = Number.isFinite(json.cents) ? json.cents : null;
+ this.quantity = Number.isFinite(json.quantity) ? json.quantity : null;
this.featured = json.featured ? json.featured : false;
this.category = json.category ? json.category : null;
this.photo_base = json.photo_base ? json.photo_base : null;
diff --git a/iridescence/src/models/product_diff.js b/iridescence/src/models/product_diff.js
deleted file mode 100644
index a683102..0000000
--- a/iridescence/src/models/product_diff.js
+++ /dev/null
@@ -1,43 +0,0 @@
-export default class ProductDiff {
- constructor(product) {
- if (product) {
- this.id = product.id ? product.id : 0;
- this.name = product.name ? product.name : null;
- this.description = product.description ? product.description : null;
- this.cents = product.cents ? product.cents : null;
- this.quantity = product.quantity ? product.quantity : null;
- this.featured =
- typeof product.featured === "boolean" ? product.featured : null;
- this.category_path = product.category ? product.category : null;
- this.photo_set = null;
- }
- }
-
- isValidPost() {
- return (
- !this.id &&
- this.name &&
- this.description &&
- this.cents &&
- !this.quantity.isNaN &&
- this.photo_set &&
- this.category_path &&
- this.description &&
- typeof this.featured === "boolean"
- );
- }
-
- isValidPatch(product) {
- return (
- this.id &&
- (this.photo_set ||
- (this.name && this.name != product.name) ||
- (this.cents && this.cents != product.cents) ||
- (this.category_path && this.category_path != product.category) ||
- (this.quantity && this.quantity != product.quantity) ||
- (this.description && this.description != product.description) ||
- (typeof this.featured === "boolean" &&
- this.featured != product.featured))
- );
- }
-}
diff --git a/iridescence/src/store/index.js b/iridescence/src/store/index.js
index f98fd87..19205c9 100644
--- a/iridescence/src/store/index.js
+++ b/iridescence/src/store/index.js
@@ -41,22 +41,6 @@ export default new Vuex.Store({
if (products) {
state.products = products;
}
- },
- replaceProduct(state, product) {
- if (!product || !product.id) {
- return;
- }
-
- let index = state.products.findIndex(p => p.id == product.id);
-
- if (index) {
- state.products[index] = product;
- }
- },
- addProduct(state, product) {
- if (product) {
- state.products.push(product);
- }
}
},
actions: {
@@ -66,16 +50,16 @@ export default new Vuex.Store({
commit("setProducts", products);
commit("toggleBusy");
},
- async updateProduct({ commit }, product) {
+ async updateProduct({ commit, dispatch }, product) {
commit("toggleBusy");
- const updatedProduct = await dichroism.updateProduct(product);
- commit("replaceProduct", updatedProduct);
+ await dichroism.updateProduct(product);
+ dispatch("refreshProducts");
commit("toggleBusy");
},
- async createProduct({ commit }, product) {
+ async createProduct({ commit, dispatch }, product) {
commit("toggleBusy");
- const newProduct = await dichroism.createProduct(product);
- commit("addProduct", newProduct);
+ await dichroism.createProduct(product);
+ dispatch("refreshProducts");
commit("toggleBusy");
},
async createPhotoSet({ commit }, file) {