Ngiler SH3LL 360
Home
Information
Create File
Create Folder
:
/
home
/
tbf
/
tbfguest.tbf.ro
/
src
/
components
/
public
/
sales
/
Information Server
MySQL :
OFF
Perl :
OFF
CURL :
ON
WGET :
OFF
PKEXEC :
OFF
Directive
Local Value
IP Address
89.40.16.97
System
Linux server.atelieruldeit.ro 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64
User
tbf
PHP Version
7.3.33
Software
Apache
Doc root
Writable
close
Edit File :
GeneralInformation.vue
| Size :
16.68
KB
Copy
<!-- @format --> <template> <!-- Informatii generale --> <div class="grid grid-cols-1 gap-x-8 gap-y-8 md:grid-cols-3"> <div class="px-4 sm:px-0"> <h2 class="text-base font-semibold leading-7 text-gray-900">Informații Generale Produs</h2> <p class="mt-1 text-sm leading-6 text-gray-600"> Completează informațiile generale despre produsul / serviciul pentru care vrei să construiești proces complet de vânzare la ziua vânzărilor </p> </div> <form class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2" @submit.prevent="submitAction"> <div class="px-4 py-6 sm:p-8"> <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6"> <!-- Nume produs --> <div class="sm:col-span-4"> <label for="name" class="block text-sm font-medium leading-6 text-gray-900"> Numele produsului <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Exemplu: Pantofi, Cercei, Serviciu de dezvoltare software, Serviciu de montare ferestre, etc.</p> </div> </template> </Popper> </label> <div class="mt-2 relative"> <template v-if="!onlyPreviewMode"> <input type="text" id="name" v-model="name" :class="[ v$.name.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" class="block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" /> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3" v-if="v$.name.$errors.length"> <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" class="h-5 w-5 text-red-500"> <path fill="currentColor" d="M256 32a224 224 0 1 1 0 448 224 224 0 1 1 0-448zm0 480A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c-8.8 0-16 7.2-16 16V272c0 8.8 7.2 16 16 16s16-7.2 16-16V144c0-8.8-7.2-16-16-16zm24 224a24 24 0 1 0 -48 0 24 24 0 1 0 48 0z" /> </svg> </div> </template> <p v-else class="text-sm text-gray-700 leading-6">{{ name }}</p> </div> </div> <!-- Marca produsului --> <div class="sm:col-span-4"> <label for="brand" class="block text-sm font-medium leading-6 text-gray-900"> Marca produsului <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Scrie marca produsului dacă se aplică. Dacă nu se aplică scrie: "Nu se aplică"</p> </div> </template> </Popper> </label> <div class="mt-2 relative"> <template v-if="!onlyPreviewMode"> <input type="text" id="brand" v-model="brand" name="produs" placeholder="Ex: Nike / Nu se aplică" :class="[ v$.brand.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" class="block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" /> <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-3" v-if="v$.brand.$errors.length"> <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" class="h-5 w-5 text-red-500"> <path fill="currentColor" d="M256 32a224 224 0 1 1 0 448 224 224 0 1 1 0-448zm0 480A256 256 0 1 0 256 0a256 256 0 1 0 0 512zm0-384c-8.8 0-16 7.2-16 16V272c0 8.8 7.2 16 16 16s16-7.2 16-16V144c0-8.8-7.2-16-16-16zm24 224a24 24 0 1 0 -48 0 24 24 0 1 0 48 0z" /> </svg> </div> </template> <p v-else class="text-sm text-gray-700 leading-6">{{ brand }}</p> </div> </div> <!-- Segment de piata --> <div class="col-span-6"> <div> <label class="block text-sm font-medium leading-6 text-gray-900" :class="[v$.segmentSelected.$errors.length ? 'text-red-500' : 'text-gray-900']"> Segment de piață <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p>Alege segmentul de piață către care se adresează acest produs.</p> </div> </template> </Popper> </label> <fieldset class="mt-2"> <div class="space-y-3"> <div v-for="item in segment" class="flex items-center"> <input name="segment" type="radio" :value="item.key" v-model="segmentSelected" :id="`ageId${item.key}`" :class="[v$.segmentSelected.$errors.length ? 'border-red-300' : 'border-gray-300']" class="h-4 w-4 text-blue-500 focus:ring-blue-500 peer" :disabled="onlyPreviewMode" /> <label :for="`ageId${item.key}`" class="ml-3 block text-sm leading-6 text-gray-500 peer-checked:text-gray-900">{{ item.name }}</label> </div> </div> </fieldset> <p class="mt-2 text-sm text-red-600" v-if="v$.segmentSelected.$errors.length">Selectează o opțiune.</p> </div> </div> <!-- Descriere products --> <div class="col-span-full"> <div class="flex justify-between"> <label for="descriere-products" class="block text-sm font-medium leading-6 text-gray-900"> Descriere produs <Popper hover :openDelay="200" offsetDistance="10" placement="right" v-if="!onlyPreviewMode"> <span class="text-gray-400">(?)</span> <template #content> <div role="tooltip" class="bg-gray-900 shadow-sm ring-1 ring-gray-900/5 rounded-lg px-4 py-3 text-white text-xs relative max-w-[15rem]"> <div class="w-3 h-3 bg-gray-900 absolute top-[50%] -translate-y-[50%] -left-1 rotate-45"></div> <p> Descrie produsul în detaliu. După ce vei scrie 150 de caractere din descriere vei putea să completezi apoi detalii folosind AI. Important să verifici textul generat de AI și să îl corectezi astfel încât să se aplice produsului / serviciului. </p> </div> </template> </Popper> </label> <div class="text-sm text-gray-500" :class="[{ 'text-green-500': descriptionLength() > 150 }, { 'text-red-500': v$.description.$errors.length }]" v-if="!onlyPreviewMode"> {{ descriptionLength() }}/150 </div> </div> <div class="mt-2"> <template v-if="!onlyPreviewMode"> <div class="relative"> <resize-textarea :key="`textareaDescription${keyInputs}`" type="text" :rows="5" id="descriere-products" v-model="description" placeholder="Ex: Respiră adânc și găsește-ți liniștea cu aplicația de meditație 'Calm'. Oferă ghiduri audio pentru meditație, sfaturi pentru mindfulness și sesiuni personalizate în funcție de starea ta de spirit." :class="[ v$.description.$errors.length ? 'text-red-900 ring-red-300 placeholder:text-red-300 focus:ring-red-500' : 'text-gray-900 ring-gray-300 placeholder:text-gray-400 focus:ring-blue-500', ]" class="block w-full rounded-md border-0 py-1.5 shadow-sm ring-1 ring-inset focus:ring-2 focus:ring-inset sm:text-sm sm:leading-6" /> <div class="absolute top-0 left-0 w-full rounded-lg h-full flex items-center justify-center bg-white/50 z-50 backdrop-blur-sm" v-if="loadedGenerateAi"> <LoaderTbf color="text-blue-500" /> </div> </div> <button class="mt-2 text-sm cursor-pointer text-blue-500 hover:text-blue-600 font-medium inline-flex items-center disabled:cursor-not-allowed disabled:text-blue-300 disabled:hover:text-blue-300" v-if="descriptionLength() > 150 && name && false" :disabled="loadedGenerateAi" @click="generateDescWithAi"> <LoaderTbf color="text-blue-500" class="mr-1 flex items-center justify-center" v-if="loadedGenerateAi" /> <div class="w-[20px] h-[19px] flex items-center justify-center mr-1" v-else> <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512" class="inline-block"> <path fill="currentColor" d="M327.5 85.2c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L384 128l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L448 128l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L448 64 426.8 7.5C425.1 3 420.8 0 416 0s-9.1 3-10.8 7.5L384 64 327.5 85.2zM205.1 73.3c-2.6-5.7-8.3-9.3-14.5-9.3s-11.9 3.6-14.5 9.3L123.3 187.3 9.3 240C3.6 242.6 0 248.3 0 254.6s3.6 11.9 9.3 14.5l114.1 52.7L176 435.8c2.6 5.7 8.3 9.3 14.5 9.3s11.9-3.6 14.5-9.3l52.7-114.1 114.1-52.7c5.7-2.6 9.3-8.3 9.3-14.5s-3.6-11.9-9.3-14.5L257.8 187.4 205.1 73.3zM384 384l-56.5 21.2c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L384 448l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L448 448l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L448 384l-21.2-56.5c-1.7-4.5-6-7.5-10.8-7.5s-9.1 3-10.8 7.5L384 384z" /> </svg> </div> Generează cu AI </button> </template> <p v-else-if="description" class="text-sm text-gray-700 leading-6 whitespace-pre-wrap" v-html="description.replace(/(?:\r\n|\r|\n)/g, `<div class='h-[6px]'></div>`)"></p> </div> </div> </div> </div> <!-- Buttons --> <div class="flex items-center justify-end gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8"> <p class="text-sm text-red-600" v-if="v$.$errors.length">Completează formularul pentru a salva.</p> <div class="flex items-center gap-x-6 ml-auto"> <template v-if="!onlyPreviewMode"> <button v-if="canOnlyPreview" @click="changePreviewMode" type="button" class="text-sm font-semibold leading-6 text-gray-900">Anulează</button> <button v-else @click="resetForm" type="button" class="text-sm font-semibold leading-6 text-gray-900">Resetează</button> </template> <template v-if="onlyPreviewMode"> <button @click="changePreviewMode" type="button" class="rounded-md flex items-center px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 bg-white border-gray-300 text-gray-900 hover:bg-gray-50 border"> Modifică </button> </template> <button v-else type="submit" class="rounded-md flex items-center px-3 py-2 text-sm font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 bg-blue-500 text-white hover:bg-blue-400" :class="{ 'disabled:cursor-not-allowed': loadingSubmit }" :disabled="loadingSubmit"> <LoaderTbf class="inline-block mr-2" color="text-white" v-if="loadingSubmit" /> Salvează </button> </div> </div> </form> </div> </template> <script> // Import the required components import { useVuelidate } from "@vuelidate/core"; import { required, helpers } from "@vuelidate/validators"; import LoaderTbf from "@/components/public/LoadingTbf.vue"; import { userNotificationsStore } from "@/stores/notifications.js"; const requiredAndMinim150 = (value) => helpers.req(value) && value.replace(/ /g, "").length > 150; export default { setup: () => ({ v$: useVuelidate() }), props: { productData: Object, }, components: { LoaderTbf, }, data() { return { realTimeNotifications: userNotificationsStore(), loadingSubmit: false, loadedGenerateAi: false, keyInputs: 1, name: "", brand: "", description: "", segmentSelected: "", segment: [ { name: "Ieftin (Low Cost)", key: "low_cost" }, { name: "Economic (Budget)", key: "budget" }, { name: "Premium", key: "premium" }, { name: "Lux (Luxury)", key: "luxury" }, ], onlyPreviewMode: false, canOnlyPreview: false, }; }, validations() { return { name: { required }, brand: { required }, description: { requiredAndMinim150 }, segmentSelected: { required }, }; }, mounted() { if (this.productData) { this.name = this.productData.name; this.brand = this.productData.brand; this.description = this.productData.description; this.segmentSelected = this.productData.market_segment; } if ( this.name !== "" && this.name != null && this.brand !== "" && this.brand != null && this.description !== "" && this.description != null && this.segmentSelected !== "" && this.segmentSelected != null ) { this.onlyPreviewMode = true; this.canOnlyPreview = true; } }, methods: { descriptionLength() { return this.description ? this.description.replace(/ /g, "").length : 0; }, async submitAction() { const isFormCorrect = await this.v$.$validate(); if (!isFormCorrect) { this.loadingSubmit = false; return; } this.loadingSubmit = true; var formData = { name: this.name, brand: this.brand, description: this.description, market_segment: this.segmentSelected, }; if (this.$auth.user().product_id) { this.updateProduct(formData); } else { this.storeProduct(formData); } }, storeProduct(formData) { axios .post(`instances/${this.$auth.user().instance.id}/products`, formData) .then(({ data }) => { this.$emit("updateProduct", data.data); this.$auth.fetch(); this.canOnlyPreview = true; this.onlyPreviewMode = true; }) .catch((error) => { var responseError = error.response.data; this.realTimeNotifications.addNotification({ type: "error", title: "TBF ERROR", description: `"${responseError.message}"<br>Vă rugăm să ne contactați și să ne transmiteți eroare. În interesul remedierii acestei probleme cât mai curând posibil.`, }); }) .finally(() => { this.loadingSubmit = false; }); }, updateProduct(formData) { axios .patch(`/instances/${this.$auth.user().instance.id}/products/${this.$auth.user().product_id}`, formData) .then(({ data }) => { this.$emit("updateProduct", data.data); this.canOnlyPreview = true; this.onlyPreviewMode = true; }) .catch((error) => { var responseError = error.response.data; this.realTimeNotifications.addNotification({ type: "error", title: "TBF ERROR", description: `"${responseError.message}"<br>Vă rugăm să ne contactați și să ne transmiteți eroare. În interesul remedierii acestei probleme cât mai curând posibil.`, }); }) .finally(() => { this.loadingSubmit = false; }); }, resetForm() { this.name = this.productData ? this.productData.name : ""; this.brand = this.productData ? this.productData.brand : ""; this.description = this.productData ? this.productData.description : ""; this.segmentSelected = this.productData ? this.productData.market_segment : ""; }, generateDescWithAi() { if (this.name && this.description) { this.loadedGenerateAi = true; axios .post(`/generate/product-description`, { name: this.name, description: this.description }) .then(({ data }) => { this.description = data.message; }) .finally(() => { this.keyInputs++; this.loadedGenerateAi = false; }); } }, changePreviewMode() { this.v$.$reset(); this.onlyPreviewMode = !this.onlyPreviewMode; this.name = this.productData.name; this.brand = this.productData.brand; this.segmentSelected = this.productData.market_segment; this.description = this.productData.description; }, }, }; </script>
Back