diff options
author | Adam T. Carpenter <atc@53hor.net> | 2020-11-12 16:27:13 -0500 |
---|---|---|
committer | Adam T. Carpenter <atc@53hor.net> | 2020-11-12 16:27:13 -0500 |
commit | 9b77f9ec2c00b48c551f65b2e9d7a087004de4c0 (patch) | |
tree | b7eb96fc4a2c7baffcb4acfc93c572ab079f11a2 /iridescence | |
parent | 7381a7033231e6454a37fd64b1f3de4e8d59355f (diff) | |
download | theglassyladies-9b77f9ec2c00b48c551f65b2e9d7a087004de4c0.tar.xz theglassyladies-9b77f9ec2c00b48c551f65b2e9d7a087004de4c0.zip |
Noice. Product creation and updating is totally functional.
Diffstat (limited to 'iridescence')
-rw-r--r-- | iridescence/src/components/ProductSearch.vue | 2 | ||||
-rw-r--r-- | iridescence/src/components/admin/NewProduct.vue | 17 | ||||
-rw-r--r-- | iridescence/src/components/admin/ProductEditCard.vue | 229 | ||||
-rw-r--r-- | iridescence/src/components/admin/ProductEditList.vue | 6 | ||||
-rw-r--r-- | iridescence/src/models/product.js | 4 | ||||
-rw-r--r-- | iridescence/src/models/product_diff.js | 43 | ||||
-rw-r--r-- | iridescence/src/store/index.js | 28 |
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) { |