ソースを参照

admin panell updates

master
unknown 9ヶ月前
コミット
ef3349aaab
15個のファイルの変更696行の追加445行の削除
  1. +2
    -2
      package-lock.json
  2. +1
    -1
      package.json
  3. +20
    -33
      src/views/live-preview/pages/auth2/forgot-password.vue
  4. +64
    -2
      src/views/live-preview/pages/auth2/reset-password.vue
  5. +4
    -2
      src/views/live-preview/pages/blogs/addBlog.vue
  6. +12
    -2
      src/views/live-preview/pages/comments/comments.vue
  7. +107
    -74
      src/views/live-preview/pages/discounts/addDiscount.vue
  8. +8
    -7
      src/views/live-preview/pages/discounts/discounts.vue
  9. +119
    -69
      src/views/live-preview/pages/discounts/editDiscount.vue
  10. +24
    -4
      src/views/live-preview/pages/identity/idenities.vue
  11. +24
    -9
      src/views/live-preview/pages/orders/approvedOrders.vue
  12. +15
    -4
      src/views/live-preview/pages/orders/orders.vue
  13. +170
    -123
      src/views/live-preview/pages/products/addProduct.vue
  14. +114
    -104
      src/views/live-preview/pages/products/editProduct.vue
  15. +12
    -9
      src/views/live-preview/pages/products/products.vue

+ 2
- 2
package-lock.json ファイルの表示

@@ -1,11 +1,11 @@
{
"name": "LightAble",
"name": "NovinPlast",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "LightAble",
"name": "NovinPlast",
"version": "0.1.0",
"dependencies": {
"@amcharts/amcharts4": "^4.10.38",


+ 1
- 1
package.json ファイルの表示

@@ -1,5 +1,5 @@
{
"name": "LightAble",
"name": "NovinPlast",
"version": "0.1.0",
"private": true,
"scripts": {


+ 20
- 33
src/views/live-preview/pages/auth2/forgot-password.vue ファイルの表示

@@ -21,7 +21,7 @@ export default {
const store = useStore();
const mobile = ref("");
const otpSent = ref(false);
const timer = ref(120);
const timer = ref(60);
const otpCode = ref("");
const resendAvailable = ref(false);
let timerInterval = null;
@@ -45,24 +45,12 @@ export default {
})
.catch((err) => {
sendOtpLoading.value = false;
console.log(err);
const status = err.status;
if (status == 429) {
Swal.fire({
icon: "error",
title: "تعداد تلاش‌ها زیاد شد",
text: "لطفاً بعداً دوباره امتحان کنید",
confirmButtonText: "باشه",
});
}
if (status == 400) {
Swal.fire({
icon: "warning",
title: "کد تایید قبلاً ارسال شده",
text: "برای این شماره، کد ورود قبلاً ارسال شده است. لطفاً کمی صبر کنید تا کد قبلی منقضی شود",
confirmButtonText: "باشه",
});
}
Swal.fire({
icon: "error",
title: "خطا در ارسال کد",
text: err.response.data.message,
confirmButtonText: "متوجه شدم",
});
});
};

@@ -78,10 +66,11 @@ export default {
};

const resendOtp = () => {
otpSent.value = false;
otpCode.value = "";
timer.value = 120;
timer.value = 60;
resendAvailable.value = false;
sendOtp(); // Resend OTP
startTimer(); // Restart the countdown
};

const verifyOtp = async () => {
@@ -134,7 +123,6 @@ export default {
try {
const response = await ApiServiece.get("settings/logo_fav");
settings.value = response.data.data;
} catch (error) {
console.error("Error fetching settings:", error);
}
@@ -230,14 +218,14 @@ export default {
role="status"
aria-hidden="true"
></span>
<span v-else> ورود</span>
<span v-else>ورود</span>
</button>
</div>
</div>

<div class="d-grid mt-3">
<button
v-if="!otpSent && !resendAvailable"
v-if="!otpSent"
@click="sendOtp"
type="button"
class="btn btn-primary"
@@ -252,15 +240,14 @@ export default {
<span v-else> ارسال کد ورود </span>
</button>

<div class="d-grid mt-3" v-if="resendAvailable && otpSent">
<button
@click="resendOtp"
type="button"
class="btn btn-primary"
>
ارسال مجدد کد
</button>
</div>
<button
v-if="resendAvailable && otpSent"
@click="resendOtp"
type="button"
class="btn btn-primary"
>
ارسال مجدد کد
</button>
</div>
</div>
</div>


+ 64
- 2
src/views/live-preview/pages/auth2/reset-password.vue ファイルの表示

@@ -1,12 +1,15 @@
<script>
import { useStore } from "vuex";
import { ref } from "vue";
import ApiServiece from "@/services/ApiService";
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import Swal from "sweetalert2";
export default {
name: "RESET-PASSWORD",
components: {},
setup() {
const logo = ref();
const settings = ref()
const router = useRouter();
const loading = ref(false);
const errors = ref({});
@@ -58,6 +61,20 @@ export default {
errors.value[field] = "";
};

const getSettings = async () => {
logo.value = localStorage.getItem("logo");
try {
const response = await ApiServiece.get("settings/logo_fav");
settings.value = response.data.data;
} catch (error) {
console.error("Error fetching settings:", error);
}
};

onMounted(() => {
getSettings();
});

return {
newPassword,
confirmPassword,
@@ -65,6 +82,7 @@ export default {
clearError,
errors,
loading,
logo,
};
},
};
@@ -74,10 +92,12 @@ export default {
<div class="auth-main v2">
<div class="bg-overlay bg-dark"></div>
<div class="auth-wrapper">
<div class="auth-sidecontent"></div>
<div class="auth-form">
<div class="card my-5 mx-3">
<div style="direction: rtl" class="card-body">
<div class="text-center mb-4">
<img :src="logo" alt="Logo" class="styled-logo" />
</div>
<h4 class="f-w-500 mb-1">بازنشانی رمز عبور</h4>
<p class="mb-3">
باز گشت به صفحه
@@ -134,3 +154,45 @@ export default {
</div>
<Rightbar />
</template>

<style scoped>
.styled-logo {
display: block;
max-width: 120px; /* Adjust the logo size here */
width: 100%;
height: auto;
border-radius: 10px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
margin: 0 auto 30px;
margin-bottom: 90px;
}

.card {
border: none;
border-radius: 10px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}

.card-body {
padding: 40px;
}

h4 {
font-size: 1.5rem;
font-weight: 500;
}

.text-center {
text-align: center;
}

.mb-3 {
margin-bottom: 1.5rem;
}

.form-control {
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
padding: 0.8rem 1rem;
}
</style>

+ 4
- 2
src/views/live-preview/pages/blogs/addBlog.vue ファイルの表示

@@ -309,12 +309,14 @@ export default {
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
})
.then((resp) => {
console.log(resp);
.then(() => {
toast.success("!بلاگ با موفقیت اضافه شد", {
position: "top-right",
autoClose: 1000,
});
setTimeout(() => {
window.location.reload();
}, 1500);
})

.catch((error) => {


+ 12
- 2
src/views/live-preview/pages/comments/comments.vue ファイルの表示

@@ -17,6 +17,8 @@ export default {
showDescription,
},
setup() {
const productSeletorLoader = ref(false);
const blogSelectorLoader = ref(false);
const products = ref([]);
const selectedProduct = ref();
const blogs = ref([]);
@@ -65,13 +67,15 @@ export default {

const handleBlogSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
blogSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/blogs?title=${searchTerm}`
);
blogs.value = response.data.data;
blogSelectorLoader.value = false;
} catch (error) {
blogSelectorLoader.value = false;
blogs.value = [];
}
};
@@ -87,14 +91,16 @@ export default {

const handleProductsSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
productSeletorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
productSeletorLoader.value = false;
} catch (error) {
products.value = [];
productSeletorLoader.value = false;
}
};

@@ -282,6 +288,8 @@ export default {
formattedProducts,
handleProductsSearch,
selectedProduct,
productSeletorLoader,
blogSelectorLoader
};
},
};
@@ -328,6 +336,7 @@ export default {
"
v-model="selectedBlog"
:options="formattedBlog"
:isLoading="blogSelectorLoader"
placeholder="بلاگی را انتخاب کنید"
@search="handleBlogSearch"
/>
@@ -341,6 +350,7 @@ export default {
"
v-model="selectedProduct"
:options="formattedProducts"
:isLoading="productSeletorLoader"
placeholder="محصولی را انتخاب کنید"
@search="handleProductsSearch"
/>


+ 107
- 74
src/views/live-preview/pages/discounts/addDiscount.vue ファイルの表示

@@ -107,28 +107,26 @@
>
<option value="cat">دسته</option>
<option value="product">محصول</option>
<option value="both">هردو</option>
<option value="all">همه</option>
</select>
</div>
<small v-if="errors.discountType" class="text-danger">
{{ errors.discountType }}
<small v-if="errors.whichPart" class="text-danger">
{{ errors.whichPart }}
</small>
</BCol>

<BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6">
<BCol v-if="whichPart === 'cat'" md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<select
:class="{ 'is-invalid': errors.selectedCat }"

<VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="categorySelectorLoader"
v-model="selectedCat"
class="form-control"
@select="clearError('selectedCat')"
placeholder="انتخاب دسته"
>
<option v-for="cat in cats" :key="cat.id" :value="cat.id">
{{ cat.title }}
</option>
</select>
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleSearch"
/>
</div>
<small v-if="errors.blogCat" class="text-danger">
{{ errors.blogCat }}
@@ -136,7 +134,7 @@
</BCol>

<BCol
v-if="whichPart === 'product' || whichPart === 'both'"
v-if="whichPart === 'product'"
sm="6"
class="mt-3"
style="margin-top: 30px"
@@ -147,8 +145,10 @@

<VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="categorySelectorLoader"
v-model="selectedProduct"
:options="formattedProducts"
@search="handleProductSearch"
placeholder="محصولی را انتخاب کنید"
/>
<small v-if="errors.selectedProduct" class="text-danger">
@@ -212,11 +212,11 @@

<script>
import VueSelect from "vue3-select-component";
import moment from "moment-jalaali";
import moment from "jalali-moment";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import ApiServiece from "@/services/ApiService";
import { ref, onMounted, computed } from "vue";
import { ref, computed } from "vue";
import Layout from "@/layout/custom.vue";
import DatePicker from "vue3-persian-datetime-picker";

@@ -229,6 +229,7 @@ export default {
VueSelect,
},
setup() {
const productSelectorLoader = ref(false);
const title = ref();
const discountType = ref();
const amount = ref();
@@ -240,36 +241,58 @@ export default {
const maxUsage = ref();
const whichPart = ref();
const loading = ref(false);
const cats = ref([]);
const categories = ref([]);
const errors = ref({});
const products = ref();

const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const categorySelectorLoader = ref(false);

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
categorySelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
categorySelectorLoader.value = false;
} catch (error) {
categorySelectorLoader.value = false;
categories.value = [];
}
};

const getProduct = () => {
ApiServiece.get(`admin/products`)
.then((resp) => {
products.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const handleProductSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
productSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
productSelectorLoader.value = false;
} catch (error) {
productSelectorLoader.value = false;
products.value = [];
}
};

const formattedProducts = computed(() => {
return products.value.map((product) => ({
value: product.id,
label: product.title,
}));
});
const formattedProducts = computed(() =>
Array.isArray(products.value)
? products.value.map((product) => ({
value: product.id,
label: product.title,
}))
: []
);

const validateForm = () => {
errors.value = {};
@@ -278,24 +301,14 @@ export default {
errors.value.discountType = "وارد کردن حالت تخفیف الزامی است";
if (!amount.value)
errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد";
if (!minOrder.value)
errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد";
if (
!selectedCat.value &&
(whichPart.value === "cat" || whichPart.value === "both")
)

if (!selectedCat.value && whichPart.value === "cat")
errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد";
if (
!selectedProduct.value &&
(whichPart.value === "product" || whichPart.value === "both")
)
if (!selectedProduct.value && whichPart.value === "product")
errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد";
if (!startDate.value)
errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد ";
if (!expire.value)
errors.value.expire = "انتخاب تاریخ انقضای تخفیف الزامی می باشد ";
if (!maxUsage.value)
errors.value.maxUsage = "مشخص کنید تخفیف چند بار مصرف است ";

if (!whichPart.value)
errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود";
return Object.keys(errors.value).length === 0;
@@ -305,21 +318,7 @@ export default {
errors.value[field] = "";
};

onMounted(() => {
getCats();
getProduct();
});

function convertJalaliToGregorian(jalaliDate) {
return moment(jalaliDate, "jYYYY/jMM/jDD HH:mm:ss")
.locale("fa") // Ensure Persian locale
.format("YYYY-MM-DD HH:mm:ss"); // Convert to Gregorian format
}

const submitForm = () => {
startDate.value = convertJalaliToGregorian(startDate.value);
expire.value = convertJalaliToGregorian(expire.value);

if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right",
@@ -327,22 +326,38 @@ export default {
});
return;
}

loading.value = true;
const formData = new FormData();
formData.append("title", title.value);
formData.append("type", discountType.value);
formData.append("amount", amount.value);
formData.append("min_order", minOrder.value);
if (whichPart.value === "cat" || whichPart.value === "both") {

if (whichPart.value === "cat") {
formData.append("category_id", selectedCat.value);
}

if (whichPart.value === "product" || whichPart.value === "both") {
if (whichPart.value === "product") {
formData.append("product_id", selectedProduct.value);
}

formData.append("starts_at", startDate.value);
formData.append("expires_at", expire.value);
if (startDate.value) {
const georgianDate = moment(
startDate.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("starts_at", georgianDate);
}

if (expire.value) {
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("expires_at", georgianDate);
}

formData.append("max_usage", maxUsage.value);

ApiServiece.post(`/admin/discounts`, formData)
@@ -353,6 +368,18 @@ export default {
autoClose: 1000,
});
console.log(resp);

// Clear form fields after successful submission
title.value = "";
discountType.value = "";
amount.value = "";
minOrder.value = "";
whichPart.value = "";
selectedCat.value = null;
selectedProduct.value = null;
startDate.value = "";
expire.value = "";
maxUsage.value = "";
})
.catch((error) => {
loading.value = false;
@@ -365,7 +392,7 @@ export default {
};

return {
cats,
categories,
errors,
title,
products,
@@ -381,6 +408,12 @@ export default {
whichPart,
clearError,
loading,

handleSearch,
categorySelectorLoader,
formattedCategories,
handleProductSearch,
productSelectorLoader,
formattedProducts,
};
},


+ 8
- 7
src/views/live-preview/pages/discounts/discounts.vue ファイルの表示

@@ -60,9 +60,9 @@ export default {
ApiServiece.get(
`admin/discounts?title=${searchQuery.value || ""}&product_id=${
selectedProduct.value || ""
}&category_id=${selectedcategory.value || ""}&type=${selectedDiscountFormat.value || ""}&paginate=${
paginate.value || 10
}&page=${page.value || 1}`
}&category_id=${selectedcategory.value || ""}&type=${
selectedDiscountFormat.value || ""
}&paginate=${paginate.value || 10}&page=${page.value || 1}`
)
.then((resp) => {
filterLoading.value = false;
@@ -192,8 +192,7 @@ export default {
});

watch(selectedDiscountType, () => {
selectedProduct.value = "",
selectedcategory.value = ""
(selectedProduct.value = ""), (selectedcategory.value = "");
if (selectedDiscountType.value === "") {
filterLoading.value = true;
ApiServiece.get(
@@ -297,7 +296,7 @@ export default {
<select
class="form-select form-select-sm"
v-model="selectedDiscountType"
style="width: 120px; border-radius: 15px ; margin-right: 7px;"
style="width: 120px; border-radius: 15px; margin-right: 7px"
>
<option value="" disabled selected>اعمال بر</option>
<option value="">همه</option>
@@ -339,7 +338,8 @@ export default {
<th>مدل</th>
<th>مقدار</th>
<th>حداقل سفارش</th>
<th>تاریخ ایجاد</th>
<th>حداکثر میزان استفاده</th>
<th>تاریخ شروع</th>
<th>تاریخ انقضا</th>
<th>عملیات</th>
</tr>
@@ -351,6 +351,7 @@ export default {
<td v-if="discount.type === 'percentage'">درصدی</td>
<td>{{ discount.amount }}</td>
<td>{{ discount.min_order }}</td>
<td>{{ discount.max_usage || "" }}</td>
<td>{{ convertToJalali(discount?.starts_at) }}</td>
<td>{{ convertToJalali(discount?.expires_at) }}</td>
<td>


+ 119
- 69
src/views/live-preview/pages/discounts/editDiscount.vue ファイルの表示

@@ -108,7 +108,7 @@
>
<option value="cat">دسته</option>
<option value="product">محصول</option>
<option value="both">هردو</option>
<option value="all">همه</option>
</select>
</div>
<small v-if="errors.discountType" class="text-danger">
@@ -116,47 +116,33 @@
</small>
</BCol>

<BCol v-if="whichPart === 'cat' || whichPart === 'both'" md="6">
<BCol v-if="whichPart === 'cat'" md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<select
:class="{ 'is-invalid': errors.selectedCat }"
<VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="categorySelectorLoader"
v-model="selectedCat"
class="form-control"
@select="clearError('selectedCat')"
placeholder="انتخاب دسته"
>
<option v-for="cat in cats" :key="cat.id" :value="cat.id">
{{ cat.title }}
</option>
</select>
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
/>
</div>
<small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }}
</small>
</BCol>

<BCol
v-if="whichPart === 'product' || whichPart === 'both'"
md="6"
>
<BCol v-if="whichPart === 'product'" md="6">
<div class="form-group">
<label class="form-label">محصول</label>
<select
:class="{ 'is-invalid': errors.selectedProduct }"
<VueSelect
style="--vs-min-height: 48px; --vs-border-radius: 8px"
:isLoading="productSelectorLoader"
v-model="selectedProduct"
class="form-control"
@select="clearError('selectedProduct')"
placeholder="انتخاب محصول"
>
<option
v-for="product in products"
:key="product.id"
:value="product.id"
>
{{ product.title }}
</option>
</select>
:options="formattedProducts"
placeholder="محصولی را انتخاب کنید "
@search="handleProductSearch"
/>
</div>
<small v-if="errors.selectedProduct" class="text-danger">
{{ errors.selectedProduct }}
@@ -220,12 +206,13 @@
</template>

<script>
import moment from "moment";
import VueSelect from "vue3-select-component";
import moment from "jalali-moment";
import { useRoute } from "vue-router";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import ApiServiece from "@/services/ApiService";
import { ref, onMounted } from "vue";
import { ref, onMounted, computed } from "vue";
import Layout from "@/layout/custom.vue";
import DatePicker from "vue3-persian-datetime-picker";

@@ -234,39 +221,78 @@ export default {
components: {
Layout,
DatePicker,
VueSelect,
},
setup() {
const route = useRoute();
const productSelectorLoader = ref(false);
const title = ref();
const discountType = ref();
const amount = ref();
const minOrder = ref();
const selectedCat = ref();
const selectedProduct = ref();
const selectedProduct = ref({
value: 'product-id-123', // Example selected product ID
label: 'Amazing Product', // Example selected product label
});

const startDate = ref();
const expire = ref();
const maxUsage = ref();
const whichPart = ref();
const loading = ref(false);
const cats = ref([]);
const categories = ref([]);
const errors = ref({});
const products = ref();
const discount = ref();

const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const handleProductSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
productSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/products?title=${searchTerm}`
);
products.value = response.data.data;
productSelectorLoader.value = false;
} catch (error) {
productSelectorLoader.value = false;
products.value = [];
}
};

const getProduct = () => {
ApiServiece.get(`admin/products`)
const formattedProducts = computed(() => {
// If products are available, map them to options
if (Array.isArray(products.value) && products.value.length > 0) {
return products.value.map((product) => ({
value: product.id,
label: product.title,
}));
}

// If no products are available, show a placeholder option
return [
{
value: "",
label: "برای جستجو شروع به تایپ کنید...",
disabled: true,
},
];
});

const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
products.value = resp.data.data;
categories.value = resp.data.data;
})
.catch((err) => {
console.log(err);
@@ -285,14 +311,30 @@ export default {
if (discount.value.category_id) {
whichPart.value = "cat";
}
console.log(discount.value.product_id);

selectedProduct.value = discount.value.product_id;
if (!discount.value.category_id && !discount.value.product_id) {
whichPart.value = "all";
}

if (discount.value.product_id) {
whichPart.value = "product";
}
startDate.value = discount.value.starts_at;
expire.value = discount.value.expires_at;

if (discount?.value.starts_at) {
startDate.value = moment(
discount?.value.starts_at,
"YYYY-MM-DD HH:mm:ss"
).format("jYYYY/jMM/jDD HH:mm:ss");
}

if (discount?.value.expires_at) {
expire.value = moment(
discount?.value.expires_at,
"YYYY-MM-DD HH:mm:ss"
).format("jYYYY/jMM/jDD HH:mm:ss");
}

maxUsage.value = discount.value.max_usage;
})
.catch((err) => {
@@ -328,24 +370,14 @@ export default {
errors.value.discountType = "وارد کردن حالت تخفیف الزامی است";
if (!amount.value)
errors.value.amount = "وارد کردن مقدار تخفیف الزامی می باشد";
if (!minOrder.value)
errors.value.minOrder = "وارد کردن حداقل میزان تخفیف الزامی می باشد";
if (
!selectedCat.value &&
(whichPart.value === "cat" || whichPart.value === "both")
)

if (!selectedCat.value && whichPart.value === "cat")
errors.value.selectedCat = "انتخاب دسته برای تخفیف الزامی می باشد";
if (
!selectedProduct.value &&
(whichPart.value === "product" || whichPart.value === "both")
)
if (!selectedProduct.value && whichPart.value === "product")
errors.value.selectedProduct = "انتخاب محصول برای تخفیف الزامی می باشد";
if (!startDate.value)
errors.value.startDate = "انتخاب تاریخ اعمال تخفیف الزامی می باشد ";
if (!expire.value)
errors.value.expire = "انتخاب تاریخ انقضای تخفیف الزامی می باشد ";
if (!maxUsage.value)
errors.value.maxUsage = "مشخص کنید تخفیف چند بار مصرف است ";

if (!whichPart.value)
errors.value.whichPart = "مشخص کنید تخفیف بر چه بخشی اعمال شود";
return Object.keys(errors.value).length === 0;
@@ -357,7 +389,7 @@ export default {

onMounted(() => {
getCats();
getProduct();
getDiscount();
});

@@ -375,16 +407,30 @@ export default {
formData.append("type", discountType.value);
formData.append("amount", amount.value);
formData.append("min_order", minOrder.value);
if (whichPart.value === "cat" || whichPart.value === "both") {
if (whichPart.value === "cat") {
formData.append("category_id", selectedCat.value);
}

if (whichPart.value === "product" || whichPart.value === "both") {
if (whichPart.value === "product") {
formData.append("product_id", selectedProduct.value);
}

formData.append("starts_at", startDate.value);
formData.append("expires_at", expire.value);
if (startDate.value) {
const georgianDate = moment(
startDate.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("starts_at", georgianDate);
}

if (expire.value) {
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("expires_at", georgianDate);
}

formData.append("max_usage", maxUsage.value);

ApiServiece.post(`/admin/discounts`, formData)
@@ -407,7 +453,7 @@ export default {
};

return {
cats,
categories,
errors,
title,
products,
@@ -425,6 +471,10 @@ export default {
whichPart,
clearError,
loading,
formattedCategories,
formattedProducts,
handleProductSearch,
productSelectorLoader,
};
},
};


+ 24
- 4
src/views/live-preview/pages/identity/idenities.vue ファイルの表示

@@ -34,18 +34,19 @@ export default {
const attributeTitle = ref();
const attributeId = ref();
const attrebuteCat = ref();
const selectorLoader = ref(false);

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
selectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
console.log(categories.value, "products");
selectorLoader.value = false;
} catch (error) {
console.error("Error fetching products:", error);
selectorLoader.value = false;
categories.value = [];
}
};
@@ -231,6 +232,7 @@ export default {
formattedCategories,
handleSearch,
selectedcategory,
selectorLoader,
};
},
};
@@ -255,10 +257,17 @@ export default {
<VueSelect
style="--vs-border-radius: 16px"
v-model="selectedcategory"
:isLoading="selectorLoader"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleSearch"
/>
>
<template #menu-header>
<div class="menu-header">
<h3>دسته ها</h3>
</div>
</template>
</VueSelect>
</div>
<button
data-bs-toggle="modal"
@@ -500,4 +509,15 @@ export default {
cursor: pointer;
user-select: none;
}
.menu-header {
position: sticky;
top: 0;
padding: 0.5rem 1rem;
background-color: #f4f4f5;
}

.menu-header h3 {
margin: 0;
color: var(--vs-option-text-color);
}
</style>

+ 24
- 9
src/views/live-preview/pages/orders/approvedOrders.vue ファイルの表示

@@ -13,6 +13,7 @@ export default {
DatePicker,
},
setup() {
const selectorLoader = ref(false);
const isLoading = ref(false);
const date = ref([]);
const brands = ref([]);
@@ -25,6 +26,18 @@ export default {
const selectedBrand = ref();
const allProducts = ref([]);
const getAllProducts = () => {
if (date.value[0]) {
date.value[0] = moment(date.value[0], "YYYY-MM-DD HH:mm:ss").format(
"YYYY/MM/DD HH:mm:ss"
);
}

if (date.value[1]) {
date.value[1] = moment(date.value[1], "YYYY-MM-DD HH:mm:ss").format(
"YYYY/MM/DD HH:mm:ss"
);
}

filterLoading.value = true;
ApiServiece.get(
`admin/orders/order-items/approved?brand_id=${
@@ -46,10 +59,10 @@ export default {
};

const convertToJalali = (date) => {
return moment(date, "YYYY-MM-DD HH:mm:ss")
.locale("fa")
.format("YYYY/MM/DD");
};
return moment(date, "YYYY-MM-DD HH:mm:ss")
.locale("fa")
.format("jYYYY/jMM/jDD HH:mm:ss");
};

const getFile = () => {
isLoading.value = true;
@@ -149,14 +162,15 @@ export default {

const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
selectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}`
);
brands.value = response.data.data;
selectorLoader.value = false;
} catch (error) {
console.error("Error fetching products:", error);
selectorLoader.value = false;
brands.value = [];
}
};
@@ -193,6 +207,7 @@ export default {
formatWithCommas,
handleBrandSearch,
filterLoading,
selectorLoader,
};
},
};
@@ -214,17 +229,17 @@ export default {
style="--vs-border-radius: 8px; min-width: 180px"
v-model="selectedBrand"
:options="formattedBrands"
:isLoading="selectorLoader"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
/>

<!-- Date Picker with Proper Width -->
<DatePicker
format="YYYY/MM/DD HH:mm:ss"
type="date"
:format="'jYYYY/jMM/jDD HH:mm:ss'"
type="datetime"
:range="true"
v-model="date"
@input="handleInput"
class="custom-datepicker"
/>
</div>


+ 15
- 4
src/views/live-preview/pages/orders/orders.vue ファイルの表示

@@ -82,6 +82,7 @@ export default {
shipping: "badge-shipping",
delivered: "badge-delivered",
canceled: "badge-canceled",
in_cart: "badge-in-cart",
};
return statusClasses[status] || "badge-secondary";
};
@@ -96,6 +97,7 @@ export default {
shipping: "در حال ارسال",
delivered: "تحویل‌شده",
canceled: "لغو‌شده",
in_cart: "در سبد خرید",
};
return statusLabels[status] || "نامشخص";
};
@@ -270,23 +272,29 @@ export default {
<table class="table table-hover table-bordered m-0" dir="rtl">
<thead class="table-light">
<tr>
<th>شناسه</th>
<th>وضعیت</th>

<th>قیمت</th>
<th>کاربر</th>
<th>کد پیگیری</th>
<th>تاریخ ایجاد</th>

<th>عملیات</th>
</tr>
</thead>
<tbody>
<tr v-for="order in orders" :key="order.id">
<td>{{ order?.id }}</td>
<td>
<span class="badge" :class="getStatusClass(order.status)">
{{ getStatusLabel(order.status) }}
</span>
</td>

<td>{{ order?.total_price || "" }}</td>
<td>{{ order?.user?.mobile || "مهمان" }}</td>

<td>{{ order?.tracking_code || "" }}</td>

<td>{{ convertToJalali(order?.created_at) }}</td>

<td>
@@ -597,6 +605,9 @@ export default {
.badge-canceled {
background-color: #6c757d;
}
.badge-in-cart {
background-color: #0620c6; /* You can choose any color you prefer */
}
.dropdown-item {
cursor: pointer;
}


+ 170
- 123
src/views/live-preview/pages/products/addProduct.vue ファイルの表示

@@ -235,7 +235,7 @@
</small>
</BCol>

<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group">
<label class="form-label"> تخفیف ویژه </label>
<input
@@ -252,7 +252,7 @@
</small>
</BCol>

<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group">
<label class="form-label">
تاریخ انقضای تخفیف محصول ویژه
@@ -262,7 +262,6 @@
:format="'jYYYY/jMM/jDD HH:mm:ss'"
type="datetime"
v-model="expire"
@input="handleInput"
></DatePicker>
</div>
<small v-if="errors.expire" class="text-danger">
@@ -270,7 +269,7 @@
</small>
</BCol>

<BCol md="6">
<!-- <BCol md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<select
@@ -288,26 +287,34 @@
<small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }}
</small>
</BCol> -->

<BCol md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<VueSelect
:isLoading="categorySelectorLoader"
v-model="selectedCat"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@search="handleSearch"
/>
</div>
<small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }}
</small>
</BCol>

<BCol md="6">
<div class="form-group">
<label class="form-label">برند</label>
<select
:class="{ 'is-invalid': errors.selectedBrand }"
<VueSelect
v-model="selectedBrand"
class="form-control"
@change="clearError('selectedBrand')"
placeholder="انتخاب برند محصول"
>
<option
v-for="brand in brands"
:key="brand.id"
:value="brand.id"
>
{{ brand.title }}
</option>
</select>
:isLoading="brandSelectorLoader"
:options="formattedBrands"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
/>
</div>
<small v-if="errors.selectedBrand" class="text-danger">
{{ errors.selectedBrand }}
@@ -549,7 +556,10 @@
</div>
</div>
</BCardFooter>
<addIdentity :cats="cats" @identity-updated="handleIdentityUpdate" />
<addIdentity
:cats="categories"
@identity-updated="handleIdentityUpdate"
/>
<addAttribute
:attributeValues="attributeValues"
@attribute-updated="handleAttributeUpdated()"
@@ -561,15 +571,17 @@
</template>

<script>
import VueSelect from "vue3-select-component";
import addAttribute from "@/components/modals/attribute/addAttribute.vue";
import moment from "moment";
import moment from "jalali-moment";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import ApiServiece from "@/services/ApiService";
import { ref, onMounted, watch } from "vue";
import { ref, onMounted, watch, computed } from "vue";
import Layout from "@/layout/custom.vue";
import DatePicker from "vue3-persian-datetime-picker";
import addIdentity from "@/components/modals/identity/addIdentity.vue";

export default {
name: "SAMPLE-PAGE",
components: {
@@ -577,8 +589,12 @@ export default {
DatePicker,
addIdentity,
addAttribute,
VueSelect,
},
setup() {
const categorySelectorLoader = ref(false);
const categories = ref([]);
const brandSelectorLoader = ref(false);
const attributeValues = ref();
const relatedAttrebutes = ref([]);
const countInCarton = ref();
@@ -610,18 +626,8 @@ export default {
const blogCat = ref();
const author = ref("");
const editor = ref(null);
const cats = ref([]);
const editorContent = ref("");

const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
};
const editorContent = ref("");

const getAttributeValues = () => {
ApiServiece.get(`admin/attributes`).then((resp) => {
@@ -656,28 +662,54 @@ export default {
});
};

const handleInput = () => {
if (expire.value) {
// Convert from Jalali to Georgian (Gregorian)
expire.value = moment(expire.value, "jYYYY/jMM/jDD HH:mm:ss").format(
"YYYY-MM-DD HH:mm:ss"
const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
brandSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}`
);
} else {
expire.value = null;
clearError("expire");
brands.value = response.data.data;
brandSelectorLoader.value = false;
} catch (error) {
brandSelectorLoader.value = false;
brands.value = [];
}
};

const getBrands = () => {
ApiServiece.get(`admin/brands`)
.then((resp) => {
brands.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
const formattedBrands = computed(() =>
Array.isArray(brands.value)
? brands.value.map((brand) => ({
value: brand.id,
label: brand.title,
}))
: []
);

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
categorySelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
categorySelectorLoader.value = false;
} catch (error) {
categorySelectorLoader.value = false;
categories.value = [];
}
};

const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const getAttrebuteValues = () => {
ApiServiece.get(`admin/attribute-values?attribute_id=1`)
.then((resp) => {
@@ -815,18 +847,12 @@ export default {
errors.value[field] = "";
};


onMounted(() => {
getCats();
getBrands();
getAttrebuteValues();
getAttributeValues();
});
let id = "";
const submitForm = () => {
console.log(expire.value , "test")
if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right",
@@ -842,6 +868,8 @@ export default {
formData.append("slug", slug.value);
formData.append("summary", summary.value);
formData.append("description", description.value);
formData.append("is_special", spescial.value);
formData.append("is_chosen", isChosen.value);
if (productType.value == 2) {
formData.append("wholesale_price", wholesalePrice.value);
}
@@ -858,31 +886,24 @@ export default {
formData.append("chosen_price", chosenPrice.value);
}

if (isChosen.value == 1 && spescial.value == 0) {
formData.append("is_chosen", isChosen.value);
}

if (isChosen.value == 0 && spescial.value == 1) {
formData.append("is_chosen", isChosen.value);
}

if (spescial.value == 1 && isChosen.value == 0) {
formData.append("special_price", parseInt(spescialPrice.value, 10));
formData.append("special_expires_at", expire.value);
}

if (spescial.value == 1 && isChosen.value == 0) {
formData.append("is_special", spescial.value);
}

if (spescial.value == 0 && isChosen.value == 1) {
formData.append("is_special", spescial.value);
// Convert Jalali to Gregorian before sending
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");
formData.append("special_expires_at", georgianDate);
}

formData.append("brand_id", selectedBrand.value);
formData.append("category_id", selectedCat.value);
formData.append("count_in_carton", countInCarton.value);
if (productType.value === "2" || productType.value === "3") {
formData.append("count_in_carton", countInCarton.value);
}

formData.append("image", image.value);

ApiServiece.post(`admin/products`, formData, {
@@ -892,76 +913,98 @@ export default {
},
})
.then((resp) => {
id = resp.data.data.id;
console.log(id);
const id = resp.data.data.id;

// 🔸 Call attributes API if needed
selectedAttributes.value = attrebutes.value
.filter((attribute) => attribute.isChecked)
.filter((attribute) => attribute.isChecked && attribute.value)
.map((attribute) => ({
attribute_value_id: attribute.id,
inventory: attribute.value,
type: productType.value,
}));

const finalPayload = {
productAttributes: selectedAttributes.value,
};

const jsonString = JSON.stringify(finalPayload, null, 2);

console.log(jsonString);
ApiServiece.post(`admin/products/${id}/attributes`, jsonString)
.then(() => {
selectedIdentities.value = relatedAttrebutes.value
.filter((identity) => identity.isChecked)
.map((identity) => ({
attribute_id: identity.id,
attribute_value_title: identity.value,
}));

const finalPayload = {
productSolidAttributes: selectedIdentities.value,
};

const jsonString = JSON.stringify(finalPayload, null, 2);
ApiServiece.post(
`admin/products/${id}/solid-attributes`,
jsonString
).then((resp) => {
console.log(resp);
});
})
.then((resp) => {
console.log(resp);
images.value.map((image) => {
console.log(image.file);
const formData = new FormData();
formData.append("image", image.file);
ApiServiece.post(`admin/products/${id}/images`, formData, {
headers: {
"content-type": "multipart",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
if (selectedAttributes.value.length > 0) {
const json = JSON.stringify(
{ productAttributes: selectedAttributes.value },
null,
2
);
ApiServiece.post(`admin/products/${id}/attributes`, json);
}

// 🔸 Call solid-attributes API if needed
selectedIdentities.value = relatedAttrebutes.value
.filter((identity) => identity.isChecked)
.map((identity) => ({
attribute_id: identity.id,
attribute_value_title: identity.value,
}));

if (selectedIdentities.value.length > 0) {
const json = JSON.stringify(
{ productSolidAttributes: selectedIdentities.value },
null,
2
);
ApiServiece.post(`admin/products/${id}/solid-attributes`, json);
}

// 🔸 Upload images if any
const validImages = images.value.filter((image) => image.file);
if (validImages.length > 0) {
console.log(validImages, "valid images");
validImages.forEach((image) => {
const formData = new FormData();
formData.append("image", image.file);
ApiServiece.post(`admin/products/${id}/images`, formData, {
headers: {
"content-type": "multipart",
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
});
});
})
}

.then(() => {
loading.value = false;
toast.success("!محصول با موفقیت اضافه شد", {
position: "top-right",
autoClose: 1000,
});
})

.catch((error) => {
console.error(error);
loading.value = false;
toast.error(`${error?.response?.data?.message}`, {
position: "top-right",
autoClose: 1000,
});
})
.finally(() => {
loading.value = false;
title.value = "";
imagePreview.value = null;
slug.value = "";
summary.value = "";
description.value = "";
spescial.value = 0;
isChosen.value = 0;
wholesalePrice.value = "";
retailePrice.value = "";
productType.value = "";
chosenPrice.value = "";
spescialPrice.value = "";
expire.value = "";
selectedBrand.value = null;
selectedCat.value = null;
countInCarton.value = "";
image.value = null;
images.value = [];
attrebutes.value.forEach((attribute) => {
attribute.isChecked = false;
attribute.value = "";
});
relatedAttrebutes.value.forEach((identity) => {
identity.isChecked = false;
});
});
};

@@ -970,7 +1013,6 @@ export default {
slug,
summary,
editor,
cats,
errors,
image,
imagePreview,
@@ -1001,13 +1043,18 @@ export default {
spescialPrice,
chosenPrice,
expire,
handleInput,
countInCarton,
relatedAttrebutes,
selectedIdentities,
attributeValues,
handleAttributeUpdated,
handleIdentityUpdate,
handleBrandSearch,
brandSelectorLoader,
formattedBrands,
handleSearch,
formattedCategories,
categorySelectorLoader,
};
},
};


+ 114
- 104
src/views/live-preview/pages/products/editProduct.vue ファイルの表示

@@ -140,7 +140,7 @@
</small>
</BCol>

<BCol md="6">
<BCol md="6" v-if="productType != 1">
<div class="form-group">
<label class="form-label">تعداد در کارتن</label>
<input
@@ -229,7 +229,7 @@
</small>
</BCol>

<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group">
<label class="form-label"> تخفیف ویژه </label>
<input
@@ -246,7 +246,7 @@
</small>
</BCol>

<BCol v-if="spescial" md="6">
<BCol v-if="spescial == 1" md="6">
<div class="form-group">
<label class="form-label">
تاریخ انقضای تخفیف محصول ویژه
@@ -263,7 +263,7 @@
</small>
</BCol>

<BCol md="6">
<!-- <BCol md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<select
@@ -281,43 +281,40 @@
<small v-if="errors.blogCat" class="text-danger">
{{ errors.blogCat }}
</small>
</BCol> -->

<BCol md="6">
<div class="form-group">
<label class="form-label">دسته</label>
<VueSelect
:isLoading="categorySelectorLoader"
v-model="selectedCat"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
/>
</div>
<small v-if="errors.selectedCat" class="text-danger">
{{ errors.selectedCat }}
</small>
</BCol>

<BCol md="6">
<div class="form-group">
<label class="form-label">برند</label>
<select
:class="{ 'is-invalid': errors.selectedBrand }"
<VueSelect
v-model="selectedBrand"
class="form-control"
@select="clearError('selectedBrand')"
placeholder="انتخاب برند محصول"
>
<option
v-for="brand in brands"
:key="brand.id"
:value="brand.id"
>
{{ brand.title }}
</option>
</select>
:isLoading="brandSelectorLoader"
:options="formattedBrands"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
/>
</div>
<small v-if="errors.selectedBrand" class="text-danger">
{{ errors.selectedBrand }}
</small>
</BCol>

<BCard>
<div class="card-header">
<h5 class="mb-0">ویرایش ویژگی ها</h5>
</div>
<template v-if="locals.length === 0">
<BCol>
<div class="alert alert-info text-center">
هیچ ویژگی برای این محصول انتخاب نکرده اید ...
</div>
</BCol>
</template>
<BCard v-if="locals.length !== 0">
<BRow class="g-3 mt-2">
<!-- Loop through attributes -->
<BCol
@@ -410,18 +407,11 @@
</BRow>
</BCard>

<BCard>
<BCard v-if="localIdentities.length !== 0">
<div class="card-header">
<h5 class="mb-0">ویرایش مشخصه ها</h5>
</div>
<template v-if="localIdentities.length === 0">
<BCol>
<div class="alert alert-info text-center">
برای شما مشخصه‌ای ثبت نشده است. برای ساختن یک مشخصه کلیک
کنید.
</div>
</BCol>
</template>

<BRow class="g-3 mt-2">
<!-- Loop through attributes -->
<BCol
@@ -802,7 +792,7 @@
</div>
</div>
<addIdentity
:cats="cats"
:cats="categories"
@attribute-updated="handleAttributeUpdated()"
/>
<addAttribute @attribute-updated="handleAttributeUpdated()" />
@@ -814,13 +804,14 @@
</template>

<script>
import VueSelect from "vue3-select-component";
import Swal from "sweetalert2";
import { useRoute } from "vue-router";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import moment from "moment";
import moment from "jalali-moment";
import ApiServiece from "@/services/ApiService";
import { ref, onMounted, watch } from "vue";
import { ref, onMounted, watch, computed } from "vue";
import Layout from "@/layout/custom.vue";
import addIdentity from "@/components/modals/identity/addIdentity.vue";
import addAttribute from "@/components/modals/attribute/addAttribute.vue";
@@ -833,6 +824,7 @@ export default {
DatePicker,
addIdentity,
addAttribute,
VueSelect,
},
setup() {
const locals = ref([
@@ -885,19 +877,45 @@ export default {
const blogCat = ref();
const author = ref("");
const editor = ref(null);
const cats = ref([]);
const categories = ref([]);
const editorContent = ref("");
const categorySelectorLoader = ref(false);
const brandSelectorLoader = ref(false);

const getCats = () => {
ApiServiece.get(`admin/categories`)
.then((resp) => {
cats.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
ApiServiece.get("admin/categories").then((resp) => {
categories.value = resp.data.data;
});

console.log(categories.value, "cats");
};

const getBrands = () => {
ApiServiece.get("admin/brands").then((resp) => {
brands.value = resp.data.data;
});

console.log(brands.value, "brands");
};

const formattedCategories = computed(() =>
Array.isArray(categories.value)
? categories.value.map((category) => ({
value: category.id,
label: category.title,
}))
: []
);

const formattedBrands = computed(() =>
Array.isArray(brands.value)
? brands.value.map((brand) => ({
value: brand.id,
label: brand.title,
}))
: []
);

watch(selectedCat, () => {
ApiServiece.get(`admin/attributes?category_id=${selectedCat.value}`)
.then((resp) => {
@@ -937,43 +955,17 @@ export default {
});
};

const convertToGeorgian = (date) => {
return moment(date, "jYYYY/jMM/jDD HH:mm:ss").format(
"YYYY-MM-DD HH:mm:ss"
);
};

watch(expire, (newValue) => {
if (newValue) {
// Convert from Jalali to Georgian (Gregorian) format when `expire` changes
expire.value = convertToGeorgian(newValue);
console.log(expire.value); // Logs the Georgian format for debugging
}
});

const getBrands = () => {
ApiServiece.get(`admin/brands`)
.then((resp) => {
brands.value = resp.data.data;
})
.catch((err) => {
console.log(err);
});
};

const getAttrebuteValues = () => {
ApiServiece.get(`admin/attribute-values?attribute_id=1`)
.then((resp) => {
console.log(resp);
console.log(resp, "attrebute values");
attrebutes.value = resp.data.data;
console.log("Attributes before filtering:", attrebutes.value);
})
.then(() => {
getProduct();
})
.then(() => {
console.log("Locals after processing:", locals.value);
})

.catch((err) => {
console.log(err);
});
@@ -1180,12 +1172,12 @@ export default {
if (!selectedBrand.value)
errors.value.selectedBrand = "انتخاب برند برای محصول ضروری می باشد";

if (!countInCarton.value)
if (productType.value != 1 && !countInCarton.value)
errors.value.countInCarton =
"انتخاب تعداد محصول در هر کارتن ضروری می باشد";

if (images.value.length <= 0)
errors.value.images = "انتخاب عکس برای محصول ضروری می باشد";
// if (images.value.length <= 0)
// errors.value.images = "انتخاب عکس برای محصول ضروری می باشد";

const missingInventory = attrebutes.value.filter(
(attribute) =>
@@ -1241,9 +1233,15 @@ export default {
chosenPrice.value = product.value?.chosen_price;
spescial.value = product.value?.is_special;
spescialPrice.value = product.value?.special_price;
expire.value = product.value?.special_expires_at;
if (product.value?.special_expires_at) {
expire.value = moment(
product.value.special_expires_at,
"YYYY-MM-DD HH:mm:ss"
).format("jYYYY/jMM/jDD HH:mm:ss");
}
selectedBrand.value = product.value?.brand_id;
selectedCat.value = product.value?.category_id;

countInCarton.value = product.value?.count_in_carton;

// Update images
@@ -1255,8 +1253,9 @@ export default {
preview: imageUrl,
}));

locals.value = product.value.product_attributes.map(
locals.value = product.value.attribute_value_products.map(
(productAttribute) => {
console.log(locals.value, "locals");
return {
id: productAttribute.id,
title: productAttribute.attribute_value.title,
@@ -1283,21 +1282,18 @@ export default {

repeatedAttrebute.value = hasRepeatedAttribute;

console.log(attrebutes.value);

console.log(attrebutes.value);

localIdentities.value = product.value.product_solid_attributes.map(
(productIdentities) => {
return {
id: productIdentities.id,
title: productIdentities.attribute_value.attribute.title,
isChecked: true,
value: productIdentities.attribute_value.title,
attribute_value_id: productIdentities.attribute_value_id,
};
}
);
localIdentities.value =
product.value.solid_attribute_value_products.map(
(productIdentities) => {
return {
id: productIdentities.id,
title: productIdentities.attribute_value.attribute.title,
isChecked: true,
value: productIdentities.attribute_value.title,
attribute_value_id: productIdentities.attribute_value_id,
};
}
);
})
.then(() => {
getIdentities();
@@ -1376,14 +1372,12 @@ export default {
};

onMounted(() => {
getAttrebuteValues();
getCats();
getBrands();
getAttrebuteValues();
});
let id = "";
const submitForm = () => {


if (!validateForm()) {
toast.error("لطفا فیلد های لازم را وارد نمایید", {
position: "top-right",
@@ -1400,7 +1394,9 @@ export default {
formData.append("slug", slug.value);
formData.append("summary", summary.value);
formData.append("description", description.value);
formData.append("count_in_carton", countInCarton.value);
if (productType.value === "2" || productType.value === "3") {
formData.append("count_in_carton", countInCarton.value);
}
if (productType.value == 2) {
formData.append("wholesale_price", wholesalePrice.value);
}
@@ -1426,8 +1422,18 @@ export default {
}

if (spescial.value == 1 && isChosen.value == 0) {
formData.append("special_price", parseInt(spescialPrice.value, 10));
formData.append("special_expires_at", expire.value);
if (spescialPrice.value) {
formData.append("special_price", parseInt(spescialPrice.value, 10));
}

if (expire.value) {
const georgianDate = moment(
expire.value,
"jYYYY/jMM/jDD HH:mm:ss"
).format("YYYY/MM/DD HH:mm:ss");

formData.append("special_expires_at", georgianDate);
}
}

if (spescial.value == 1 && isChosen.value == 0) {
@@ -1562,7 +1568,7 @@ export default {
slug,
summary,
editor,
cats,
categories,
errors,
image,
imagePreview,
@@ -1607,6 +1613,10 @@ export default {
editIdentity,
repeatedIdentity,
repeatedAttrebute,
categorySelectorLoader,
formattedCategories,
formattedBrands,
brandSelectorLoader,
};
},
};


+ 12
- 9
src/views/live-preview/pages/products/products.vue ファイルの表示

@@ -15,6 +15,8 @@ export default {
VueSelect,
},
setup() {
const brandSelectorLoader = ref(false);
const categorySelectorLoader = ref(false);
let searchTimeout = null;
const selectedProductType = ref("");
const categories = ref([]);
@@ -208,15 +210,15 @@ export default {

const handleSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
categorySelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/categories?title=${searchTerm}`
);
categories.value = response.data.data;
console.log(categories.value, "products");
categorySelectorLoader.value = false;
} catch (error) {
console.error("Error fetching products:", error);
categorySelectorLoader.value = false;
categories.value = [];
}
};
@@ -232,14 +234,15 @@ export default {

const handleBrandSearch = async (searchTerm) => {
if (searchTerm.length < 3) return;
brandSelectorLoader.value = true;
try {
const response = await ApiServiece.get(
`admin/brands?title=${searchTerm}`
);
brands.value = response.data.data;
brandSelectorLoader.value = false;
} catch (error) {
console.error("Error fetching products:", error);
brandSelectorLoader.value = false;
brands.value = [];
}
};
@@ -279,6 +282,8 @@ export default {
formattedBrands,
handleBrandSearch,
selectedBrand,
categorySelectorLoader,
brandSelectorLoader,
};
},
};
@@ -317,6 +322,7 @@ export default {
--vs-min-height: 18px;
margin-right: 7px;
"
:isLoading="categorySelectorLoader"
v-model="selectedcategory"
:options="formattedCategories"
placeholder="دسته ای را انتخاب کنید"
@@ -330,6 +336,7 @@ export default {
margin-right: 7px;
"
v-model="selectedBrand"
:isLoading="brandSelectorLoader"
:options="formattedBrands"
placeholder="برندی را انتخاب کنید"
@search="handleBrandSearch"
@@ -638,8 +645,6 @@ export default {
font-weight: bold;
}



.Product-Image {
width: 50px;
height: 50px;
@@ -681,8 +686,6 @@ export default {
color: #fff;
}



td,
table[dir="rtl"] td,
table[dir="rtl"] th {


読み込み中…
キャンセル
保存