create product functionality and page
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
:src="croppieImage"
|
:src="croppieImage"
|
||||||
:stencil-props="{ aspectRatio: 1 }"
|
:stencil-props="{ aspectRatio: 1 }"
|
||||||
/>
|
/>
|
||||||
<div>
|
<div class="buttons">
|
||||||
<button class="filter-button" @click.prevent="crop">Aplicar</button>
|
<button class="filter-button" @click.prevent="crop">Aplicar</button>
|
||||||
<button @click.prevent="cancel">Cancelar</button>
|
<button @click.prevent="cancel">Cancelar</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,7 +31,7 @@ export default {
|
|||||||
components: { Cropper },
|
components: { Cropper },
|
||||||
props: {
|
props: {
|
||||||
imageUrl: {
|
imageUrl: {
|
||||||
type: String,
|
type: [String, Object],
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -106,5 +106,10 @@ export default {
|
|||||||
}
|
}
|
||||||
.input {
|
.input {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
margin-top: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<form class="form" @submit.prevent="sendProduct">
|
<form class="form" @submit.prevent="sendProduct">
|
||||||
{{ form }}
|
|
||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<BFormCheckbox
|
<BFormCheckbox
|
||||||
id="customSwitch1"
|
id="customSwitch1"
|
||||||
v-model="form.active"
|
v-model="form.active"
|
||||||
class="label"
|
class="label"
|
||||||
:value="form.active"
|
|
||||||
>Activo
|
>Activo
|
||||||
<span class="help-text"
|
<span class="help-text"
|
||||||
>Desactiva el producto si quieres paralizar temporalmente su
|
>Desactiva el producto si quieres paralizar temporalmente su
|
||||||
@@ -15,7 +13,7 @@
|
|||||||
</BFormCheckbox>
|
</BFormCheckbox>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<FormHeader title="general" />
|
<FormHeader title="General" />
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
Estos son los datos básicos de un producto. Procura completar el mayor
|
Estos son los datos básicos de un producto. Procura completar el mayor
|
||||||
número de campos posible. Los campos señalados con asterisco (*) son
|
número de campos posible. Los campos señalados con asterisco (*) son
|
||||||
@@ -34,35 +32,41 @@
|
|||||||
/>
|
/>
|
||||||
<FormInput
|
<FormInput
|
||||||
v-model="form.price"
|
v-model="form.price"
|
||||||
|
:value="form.price ? form.price : ''"
|
||||||
min="0"
|
min="0"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
type="number"
|
type="number"
|
||||||
label-text="Precio"
|
label-text="Precio"
|
||||||
placeholder="precio + iva"
|
placeholder="precio + iva"
|
||||||
|
@input="form.price = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="cont">
|
<div class="cont">
|
||||||
<FormInput
|
<FormInput
|
||||||
v-model="form.url"
|
v-model="form.url"
|
||||||
|
:value="form.url ? form.url : ''"
|
||||||
type="text"
|
type="text"
|
||||||
label-text="Url"
|
label-text="Url"
|
||||||
placeholder="enlace directo al producto en tu tienda o web"
|
placeholder="enlace directo al producto en tu tienda o web"
|
||||||
|
@input="form.url = $event"
|
||||||
/>
|
/>
|
||||||
<small class="error" v-if="form.url && !isValidUrl(form.url)"
|
<small v-if="form.url && !isValidUrl(form.url)" class="error">
|
||||||
>La url no es válida</small
|
La url no es válida
|
||||||
>
|
</small>
|
||||||
<br />
|
<br />
|
||||||
<FormInput
|
<FormInput
|
||||||
v-model="form.sku"
|
v-model="form.sku"
|
||||||
|
:value="form.sku ? form.sku : ''"
|
||||||
type="text"
|
type="text"
|
||||||
label-text="SKU o identificador"
|
label-text="SKU o identificador"
|
||||||
|
@input="form.sku = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<label for="imagen">Imagen</label>
|
<label for="imagen">Imagen</label>
|
||||||
<client-only>
|
<ClientOnly>
|
||||||
<FormInputImage :imageUrl="form.image" @change="handleImage" />
|
<FormInputImage :image-url="form.image" @change="handleImage" />
|
||||||
</client-only>
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="cont-col">
|
<!-- <div class="cont-col">
|
||||||
<input
|
<input
|
||||||
@@ -79,20 +83,33 @@
|
|||||||
<textarea v-model="form.description" class="textarea" type="text" />
|
<textarea v-model="form.description" class="textarea" type="text" />
|
||||||
</div>
|
</div>
|
||||||
<div class="cont">
|
<div class="cont">
|
||||||
<FormInput v-model="form.stock" type="number" label-text="Stock" />
|
<FormInput
|
||||||
|
v-model="form.stock"
|
||||||
|
:value="form.stock ? form.stock : ''"
|
||||||
|
type="number"
|
||||||
|
label-text="Stock"
|
||||||
|
@input="form.stock = $event"
|
||||||
|
/>
|
||||||
<FormInput
|
<FormInput
|
||||||
v-model="form.discount"
|
v-model="form.discount"
|
||||||
|
:value="form.discount ? form.discount : ''"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
type="number"
|
type="number"
|
||||||
label-text="Descuento"
|
label-text="Descuento"
|
||||||
|
@input="form.discount = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<FormInput v-model="form.identifiers" label-text="Identificador único" />
|
<FormInput
|
||||||
|
v-model="form.identifiers"
|
||||||
|
:value="form.identifiers ? form.identifiers : ''"
|
||||||
|
label-text="Identificador único"
|
||||||
|
@input="form.identifiers = $event"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<FormHeader title="categorías" />
|
<FormHeader title="Categorías" />
|
||||||
<p class="help-text">
|
<p class="help-text">
|
||||||
Estos datos ayudan a que tu producto sea encontrado por los clientes.
|
Estos datos ayudan a que tu producto sea encontrado por los clientes.
|
||||||
Procura completar el mayor número de campos posibles. Los campos
|
Procura completar el mayor número de campos posibles. Los campos
|
||||||
@@ -101,15 +118,14 @@
|
|||||||
|
|
||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<label for="category">Categoría*</label>
|
<label for="category">Categoría*</label>
|
||||||
<b-form-input
|
<BFormInput
|
||||||
|
id="category"
|
||||||
v-model="form.category"
|
v-model="form.category"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
list="my-list-id"
|
list="my-list-id"
|
||||||
id="category"
|
/>
|
||||||
></b-form-input>
|
|
||||||
|
|
||||||
<datalist id="my-list-id">
|
<datalist id="my-list-id">
|
||||||
<option v-for="(choice, index) in categories" :key="index">
|
<option v-for="(choice, index) in categories" :key="`category-${index}`">
|
||||||
{{ choice }}
|
{{ choice }}
|
||||||
</option>
|
</option>
|
||||||
</datalist>
|
</datalist>
|
||||||
@@ -126,22 +142,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<label for="tags-basic">Palabras clave</label>
|
<label for="tags-basic">Palabras clave</label>
|
||||||
<b-form-tags
|
<BFormTags
|
||||||
v-model="form.tags"
|
v-model="form.tags"
|
||||||
placeholder="Añade palabras clave (moda, complementos...)"
|
placeholder="Añade palabras clave (moda, complementos...)"
|
||||||
input-id="tags-basic"
|
input-id="tags-basic"
|
||||||
></b-form-tags>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<label for="tags-basic">Atributos</label>
|
<label for="tags-basic">Atributos</label>
|
||||||
<b-form-tags
|
<BFormTags
|
||||||
v-model="form.attributes"
|
v-model="form.attributes"
|
||||||
placeholder="Añade características del producto (talla, color...)"
|
placeholder="Añade características del producto (talla, color...)"
|
||||||
input-id="tags-basic"
|
input-id="tags-basic"
|
||||||
></b-form-tags>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<FormHeader title="envío" />
|
<FormHeader title="Envío" />
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<label for="">Condiciones de envío</label>
|
<label for="">Condiciones de envío</label>
|
||||||
@@ -156,16 +172,20 @@
|
|||||||
<div class="cont-col">
|
<div class="cont-col">
|
||||||
<FormInput
|
<FormInput
|
||||||
v-model="form.shipping_cost"
|
v-model="form.shipping_cost"
|
||||||
|
:value="form.shipping_cost ? form.shipping_cost : ''"
|
||||||
step="0.01"
|
step="0.01"
|
||||||
type="number"
|
type="number"
|
||||||
label-text="Gastos de envío"
|
label-text="Gastos de envío"
|
||||||
placeholder="ej. 4,50"
|
placeholder="ej. 4,50"
|
||||||
|
@input="form.shipping_cost = $event"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="submit-btn" align="center">
|
<div class="submit-btn" align="center">
|
||||||
<SubmitButton text="guardar" imageUrl="" />
|
<SubmitButton text="guardar" image-url="" />
|
||||||
</div>
|
</div>
|
||||||
|
PRODUCT FORM: {{ productForm }} <br><br><br>
|
||||||
|
FORM: {{ form }}
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -174,7 +194,13 @@ import FormInputImage from './FormInputImage.vue'
|
|||||||
import dataProcessing from '~/utils/dataProcessing'
|
import dataProcessing from '~/utils/dataProcessing'
|
||||||
export default {
|
export default {
|
||||||
components: { FormInputImage },
|
components: { FormInputImage },
|
||||||
props: ['productForm'],
|
props: {
|
||||||
|
productForm: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
emits: ['send'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {
|
form: {
|
||||||
@@ -222,19 +248,24 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
async mounted() {
|
||||||
if (this.productForm) {
|
await this.getAllCategories()
|
||||||
|
if (this.productForm && Object.keys(this.productForm).length > 0) {
|
||||||
Object.keys(this.form).forEach((key) => {
|
Object.keys(this.form).forEach((key) => {
|
||||||
this.form[key] = this.productForm[key]
|
this.form[key] = this.productForm[key]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.getAllCategories()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
isValidUrl: dataProcessing.isValidUrl,
|
isValidUrl: dataProcessing.isValidUrl,
|
||||||
async getAllCategories() {
|
async getAllCategories() {
|
||||||
const { data } = await this.$api.get(`/products/all_categories/`)
|
const config = useRuntimeConfig()
|
||||||
|
const data = await $fetch(`/products/all_categories/`,{
|
||||||
|
baseURL: config.public.baseURL,
|
||||||
|
method: 'GET',
|
||||||
|
|
||||||
|
})
|
||||||
this.categories = data
|
this.categories = data
|
||||||
},
|
},
|
||||||
parseForm() {
|
parseForm() {
|
||||||
@@ -249,7 +280,7 @@ export default {
|
|||||||
if (typeof formData.get('image') === 'string') {
|
if (typeof formData.get('image') === 'string') {
|
||||||
formData.delete('image')
|
formData.delete('image')
|
||||||
}
|
}
|
||||||
|
//console.log('FormData:', formData)
|
||||||
return formData
|
return formData
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -259,6 +290,7 @@ export default {
|
|||||||
|
|
||||||
sendProduct() {
|
sendProduct() {
|
||||||
const formData = this.parseForm()
|
const formData = this.parseForm()
|
||||||
|
//TODO: review with Diego. I changed formaData with this.form
|
||||||
this.$emit('send', formData)
|
this.$emit('send', formData)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -333,7 +365,7 @@ export default {
|
|||||||
font-family: $font-secondary;
|
font-family: $font-secondary;
|
||||||
background-color: $color-light;
|
background-color: $color-light;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
margin-top: -10px;
|
margin-top: 6px;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<BContainer class="container">
|
<BContainer class="container">
|
||||||
|
<BModal
|
||||||
|
id="modal-center"
|
||||||
|
v-model="activeModal"
|
||||||
|
centered
|
||||||
|
title="latienda.coop"
|
||||||
|
:ok-variant="modalColor"> {{ modalText }}
|
||||||
|
</BModal>
|
||||||
<BRow align-h="center">
|
<BRow align-h="center">
|
||||||
<BCol class="edit-product">
|
<BCol class="edit-product">
|
||||||
<h1 class="title">Editar producto</h1>
|
<h1 class="title">Editar producto</h1>
|
||||||
@@ -27,6 +34,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import dataProcessing from '~/utils/dataProcessing'
|
import dataProcessing from '~/utils/dataProcessing'
|
||||||
|
import { useAuthStore } from '@/stores/auth'
|
||||||
export default {
|
export default {
|
||||||
setup() {
|
setup() {
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
@@ -43,22 +51,26 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
form: {},
|
form: {},
|
||||||
|
activeModal: false,
|
||||||
|
modalText: '',
|
||||||
|
modalColor: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async created() {
|
async created() {
|
||||||
try {
|
try {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const form = await $fetch(`my_products/${this.$route.params.id}/`, {
|
const route = useRoute()
|
||||||
|
this.form = await $fetch(`my_products/${route.params.id}/`, {
|
||||||
baseURL: config.public.baseURL,
|
baseURL: config.public.baseURL,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${this.auth.access}`,
|
Authorization: `Bearer ${this.auth.access}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (form.source !== 'MANUAL') {
|
if (this.form.source !== 'MANUAL' && this.form.history) {
|
||||||
try {
|
try {
|
||||||
const result = await $fetch(`history/${form.history}/`, {
|
const result = await $fetch(`history/${this.form.history}/`, {
|
||||||
baseURL: config.public.baseURL,
|
baseURL: config.public.baseURL,
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -71,7 +83,6 @@ export default {
|
|||||||
form.sync_date = null
|
form.sync_date = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { form }
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
@@ -81,24 +92,27 @@ export default {
|
|||||||
formatDatetime: dataProcessing.formatDatetime,
|
formatDatetime: dataProcessing.formatDatetime,
|
||||||
|
|
||||||
async submitProduct(value) {
|
async submitProduct(value) {
|
||||||
|
console.log('Submitting product:', value)
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
const route = useRoute()
|
||||||
|
//TODO: review PUT method, its sending 200 status but not updating the product
|
||||||
try {
|
try {
|
||||||
await this.$api.put(`/my_products/${this.$route.params.id}/`, value, {
|
await $fetch(`/my_products/${route.params.id}/`, {
|
||||||
|
baseURL: config.public.baseURL,
|
||||||
|
method: 'PUT',
|
||||||
|
body: value,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data',
|
Authorization: `Bearer ${this.auth.access}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
this.$bvToast.toast(`Producto actualizado correctamente`, {
|
this.modalText = 'Producto actualizado correctamente'
|
||||||
title: 'latienda.coop',
|
this.modalColor = 'success'
|
||||||
autoHideDelay: 5000,
|
this.activeModal = true
|
||||||
appendToast: true,
|
} catch (error) {
|
||||||
})
|
this.modalText = 'Ha habido un error'
|
||||||
} catch {
|
this.modalColor = 'danger'
|
||||||
this.$bvToast.toast(`Ha habido un error`, {
|
this.activeModal = true
|
||||||
title: 'latienda.coop',
|
console.error('Error updating product:', error)
|
||||||
autoHideDelay: 5000,
|
|
||||||
appendToast: true,
|
|
||||||
variant: 'danger',
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,15 +1,118 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<BContainer class="container">
|
||||||
<h1>Crear producto /editar/productos/crear</h1>
|
<BModal
|
||||||
</div>
|
id="modal-center"
|
||||||
|
v-model="activeModal"
|
||||||
|
centered
|
||||||
|
title="latienda.coop"
|
||||||
|
ok-variant="danger"> {{ modalText }}
|
||||||
|
</BModal>
|
||||||
|
<BRow align-h="center">
|
||||||
|
<BCol class="add-product">
|
||||||
|
<h1 class="title">Añadir producto</h1>
|
||||||
|
<ProductForm @send="submitProduct" />
|
||||||
|
</BCol>
|
||||||
|
</BRow>
|
||||||
|
</BContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
import { useAuthStore } from '@/stores/auth'
|
||||||
|
export default {
|
||||||
}
|
setup() {
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'editar',
|
||||||
|
middleware: 'auth',
|
||||||
|
auth: { authority: 'COOP_MANAGER' },
|
||||||
|
})
|
||||||
|
const auth = useAuthStore();
|
||||||
|
return {
|
||||||
|
auth
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
activeModal: false,
|
||||||
|
modalText: '',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async submitProduct(value) {
|
||||||
|
console.log('Creating product:', value)
|
||||||
|
const config = useRuntimeConfig()
|
||||||
|
try {
|
||||||
|
await $fetch(`/products/`, {
|
||||||
|
baseURL: config.public.baseURL,
|
||||||
|
method: 'POST',
|
||||||
|
body: value,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.auth.access}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
this.$router.push({
|
||||||
|
name: 'editar-productos',
|
||||||
|
params: { action: 'created' },
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
this.modalText = 'Ha habido un error'
|
||||||
|
this.activeModal = true
|
||||||
|
console.error('Error updating product:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.container {
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 80px;
|
||||||
|
}
|
||||||
|
.add-product {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
color: $color-navy;
|
||||||
|
font-size: $xxl;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
text-align: left;
|
||||||
|
@include desktop {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
@include tablet {
|
||||||
|
width: 60%;
|
||||||
|
}
|
||||||
|
@include mobile {
|
||||||
|
width: 90%;
|
||||||
|
margin-top: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.help-text {
|
||||||
|
border: 1px solid $color-greylayout;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
text-align: justify;
|
||||||
|
hyphens: auto;
|
||||||
|
font-size: $xs;
|
||||||
|
font-weight: $regular;
|
||||||
|
color: $color-greylayout;
|
||||||
|
font-family: $font-secondary;
|
||||||
|
background-color: $color-light;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: $color-greytext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.data {
|
||||||
|
margin: 0 100px;
|
||||||
|
margin-top: 80px;
|
||||||
|
font-size: $xs;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
color: $color-greytext;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
Reference in New Issue
Block a user