init git
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-comments">
|
||||
<ul>
|
||||
<li v-for="(comment, i) in comments" :key="i">
|
||||
<PanelCommentItem :data="comment" />
|
||||
</li>
|
||||
</ul>
|
||||
<AppPagination :pages="pages" />
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import init from '../../initialize/panel'
|
||||
provide('init', init())
|
||||
const { device, t } = inject('service')
|
||||
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true },
|
||||
mobile: { header: { back: true, title: t('myComments') } },
|
||||
})
|
||||
|
||||
const comments = ref([])
|
||||
const pages = ref(1)
|
||||
|
||||
const { meta, query } = useRoute()
|
||||
const { page = 1 } = query
|
||||
|
||||
const { status, data, error } = await useFetch(`/api/comments/${page}`, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
comments.value = data.value
|
||||
pages.value = data.value.pages
|
||||
|
||||
} else throw createError(error.value)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-comments {
|
||||
@apply w-full container max-lg:px-6 pb-20 pt-[1.9rem] lg:py-[6.3rem];
|
||||
|
||||
ul {
|
||||
@apply flex flex-col justify-center mx-auto gap-6 lg:gap-[2.6rem];
|
||||
|
||||
li {
|
||||
@apply border-b pb-6 lg:pb-10 border-[#B9B9B9] last:border-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body:has(.panel-comments) {
|
||||
.p-dialog-mask {
|
||||
@apply z-10 #{!important};
|
||||
|
||||
.p-dialog {
|
||||
@apply m-0 max-lg:w-full #{!important};
|
||||
}
|
||||
|
||||
.p-dialog-content {
|
||||
@apply lg:container w-screen lg:w-max flex lg:px-4 #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
.p-menu {
|
||||
@apply w-[10.8rem];
|
||||
}
|
||||
|
||||
.p-menuitem {
|
||||
@apply text-base;
|
||||
|
||||
&:first-child span {
|
||||
@apply text-[#4C4C4C] #{!important};
|
||||
}
|
||||
|
||||
&:last-child span {
|
||||
@apply text-[#AD3434] #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
.p-menuitem-content a {
|
||||
@apply rtl px-5 #{!important};
|
||||
|
||||
.p-menuitem-icon {
|
||||
@apply mr-0 ml-3 #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,257 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="user-profile-edit">
|
||||
<div>
|
||||
<h1>{{ $t("userProfileEdit") }}</h1>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<img :src="image" />
|
||||
<input ref="input" type="file" hidden @change="chooseImage" />
|
||||
<Button v-bind="btns.upload.props" @click="showImages()" />
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="(item, key) in fields" :key="key">
|
||||
<component :id="key" :is="item.is" v-bind="item.props" v-model="form[key]" />
|
||||
<label :for="key"> {{ $t(key) }} </label>
|
||||
<small v-if="errors[key]">{{ errors[key] }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<Button v-bind="btns.form.cancel.props" @click="$router.go(-1)" />
|
||||
<Button v-bind="btns.form.save.props" :loading="loading" @click="save()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import iranCities from "../../initialize/iranCities"
|
||||
import init from "../../initialize/userProfileEdit";
|
||||
const { device, toast, t } = inject('service')
|
||||
const config = reactive({
|
||||
mobile: { header: { title: t('edit') } }
|
||||
})
|
||||
const { btns, fields } = init();
|
||||
const source = ref();
|
||||
const image = ref();
|
||||
const input = ref();
|
||||
const form = reactive({});
|
||||
const errors = ref({});
|
||||
const loading = ref(false);
|
||||
const router = useRouter();
|
||||
|
||||
const { status, data, error } = await useFetch('/api/users/current', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
},
|
||||
})
|
||||
|
||||
const provinces = reactive(iranCities)
|
||||
fields.state.props.options = provinces
|
||||
|
||||
watch(() => form.state, (v) => {
|
||||
provinces.forEach(({ name, cities }) => {
|
||||
if (name == v) {
|
||||
fields.city.props.options = reactive(cities)
|
||||
}
|
||||
});
|
||||
}, { deep: true })
|
||||
|
||||
fields.city.props.disabled = computed(() => fields.city.props.options.length < 1)
|
||||
|
||||
if (status.value == 'success') {
|
||||
Object.keys(fields).forEach(key => {
|
||||
form[key] = data.value[key]
|
||||
});
|
||||
image.value = data.value.profilePhoto ?? '/images/empty-profile.svg'
|
||||
}
|
||||
else throw createError(error.value)
|
||||
|
||||
const showImages = () => input.value.click()
|
||||
|
||||
const chooseImage = (e) => {
|
||||
const binaryData = [];
|
||||
binaryData.push(e.target.files[0]);
|
||||
image.value = URL.createObjectURL(new Blob(binaryData, { type: "image" }))
|
||||
source.value = e.target.files[0];
|
||||
}
|
||||
|
||||
const save = async () => {
|
||||
errors.value = {}
|
||||
loading.value = true
|
||||
if (source.value) {
|
||||
const formData = new FormData()
|
||||
formData.append('images', source.value)
|
||||
const { status, data, error } = await useFetch('/api/images/upload', {
|
||||
headers: { Authorization: useCookie('token') },
|
||||
params: { type: 3 },
|
||||
body: formData,
|
||||
method: 'post'
|
||||
})
|
||||
if (status.value == "success") {
|
||||
form.profilePhoto = data.value.urls[0]
|
||||
}
|
||||
}
|
||||
|
||||
const { status, data, error } = await useFetch('/api/users/', {
|
||||
headers: { Authorization: useCookie('token') }, body: Object.assign({}, form), method: 'patch'
|
||||
})
|
||||
|
||||
if (status.value == "success") {
|
||||
router.go(-1)
|
||||
toast.add({
|
||||
life: 5000, severity: 'success', summary: t('success'), detail: t('updateSuccessfull')
|
||||
})
|
||||
} else {
|
||||
if (error.value.statusCode == 422)
|
||||
errors.value = error.value.data
|
||||
else
|
||||
toast.add({
|
||||
life: 5000,
|
||||
severity: 'error',
|
||||
summary: t('error'),
|
||||
detail: error.value.data.message || error.value.message
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.user-profile-edit {
|
||||
@apply w-full flex flex-col gap-[5.5rem] mb-[8.8rem];
|
||||
|
||||
&>div:nth-of-type(1) {
|
||||
@apply h-[5.9rem] bg-[#F2F2F2] flex items-center justify-center;
|
||||
|
||||
h1 {
|
||||
@apply text-[#333333] text-2xl font-bold font-iran-sans;
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-of-type(2) {
|
||||
@apply flex gap-20 justify-center self-center flex-col md:flex-row w-full sm:w-auto px-4;
|
||||
|
||||
&>div:nth-of-type(1) {
|
||||
@apply flex flex-col gap-6 self-center md:self-start;
|
||||
|
||||
img {
|
||||
@apply w-[12.4rem] h-[12.4rem] rounded-[0.6rem];
|
||||
}
|
||||
|
||||
button {
|
||||
@apply w-[12.4rem] h-12 p-0 justify-center #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-sm font-medium font-iran-sans grow-0;
|
||||
}
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-xl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-of-type(2) {
|
||||
@apply overflow-y-hidden h-max -mt-4 flex flex-col gap-14;
|
||||
|
||||
&>div:nth-of-type(1) {
|
||||
@apply md:overflow-y-auto md:max-h-[40rem] w-full;
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-transparent my-4;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply flex flex-col gap-6 w-full max-w-full md:pl-7 md:py-4 my-0 pr-px;
|
||||
|
||||
li {
|
||||
@apply w-full sm:w-[27.1rem] h-14 relative col-span-2;
|
||||
|
||||
label {
|
||||
@apply w-max absolute right-3 top-4 px-1 bg-white transition-['top'] select-none;
|
||||
@apply text-neutral-400 text-base font-medium font-vazir cursor-text pointer-events-none;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
@apply rtl w-full rounded-[0.6rem] border shadow-none border-[#CCCCCC] text-zinc-800 font-vazir;
|
||||
}
|
||||
|
||||
input[type] {
|
||||
@apply ltr;
|
||||
}
|
||||
|
||||
&:has(input:focus),
|
||||
&:has(textarea:focus),
|
||||
&:has(.p-inputwrapper-filled),
|
||||
&:has(input:not(:placeholder-shown)),
|
||||
&:has(textarea:not(:placeholder-shown)) {
|
||||
label {
|
||||
@apply -top-2 h-4;
|
||||
@apply text-zinc-800 text-xs font-normal font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
.p-inputnumber,
|
||||
.p-dropdown,
|
||||
.p-calendar {
|
||||
@apply w-full;
|
||||
|
||||
}
|
||||
|
||||
.p-dropdown .p-dropdown-label {
|
||||
@apply font-vazir leading-7;
|
||||
}
|
||||
|
||||
.p-calendar {
|
||||
@apply flex items-center relative z-0 gap-4 font-['iconsax'] #{!important};
|
||||
|
||||
&::before {
|
||||
@apply text-xl absolute left-3 z-[1] mb-1;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply pl-10 #{!important};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.p-inputnumber input {
|
||||
@apply ltr;
|
||||
}
|
||||
|
||||
small {
|
||||
@apply text-red-500 font-vazir float-left;
|
||||
}
|
||||
|
||||
&:has(small) * {
|
||||
@apply border-red-500 #{!important};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-of-type(2) {
|
||||
@apply flex flex-wrap gap-3;
|
||||
|
||||
&>button {
|
||||
@apply w-full sm:w-[13.2rem] h-12;
|
||||
|
||||
.p-button-label {
|
||||
@apply text-sm font-medium font-iran-sans;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,121 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-factors">
|
||||
<div>
|
||||
<form>
|
||||
<i class="isax isax-search-normal-1" />
|
||||
<input v-model="filters.q" :placeholder="$t('search')" />
|
||||
<Button icon="isax isax-setting-4" severity="secondary" text rounded @click="showFilters" />
|
||||
</form>
|
||||
<aside v-if="lastFactors?.length > 0">
|
||||
<h2>{{ $t("recentFactors") }}</h2>
|
||||
<ul>
|
||||
<li v-for="(factor, i) in lastFactors" :key="i">
|
||||
<PanelRecentFactorItem :data="factor" />
|
||||
</li>
|
||||
</ul>
|
||||
</aside>
|
||||
</div>
|
||||
<div>
|
||||
<PanelSalesFactor :data="factor" />
|
||||
</div>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, dialog, popup, t } = inject('service');
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true }, mobile: { header: { back: true, title: t('factors') } },
|
||||
})
|
||||
|
||||
const { meta, query } = useRoute()
|
||||
const { page = 1 } = query
|
||||
|
||||
|
||||
const lastFactors = ref([])
|
||||
const factor = ref([])
|
||||
const filters = ref({});
|
||||
|
||||
const route = useRoute()
|
||||
const id = route.params.id || 1
|
||||
|
||||
const { status, data, error } = await useFetch(`/api/orders/id/${id}`, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
lastFactors.value = data.value.lastFactors
|
||||
factor.value = data.value.factor
|
||||
|
||||
} else throw createError(error.value)
|
||||
|
||||
const component = resolveComponent('PanelFactorFilters')
|
||||
|
||||
const showFilters = (e) => {
|
||||
if (device.value == 'desktop') {
|
||||
popup.value.is = component
|
||||
popup.value.toggle(e)
|
||||
} else if (device.value == 'mobile')
|
||||
dialog.show(component)
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-factors {
|
||||
@apply w-full lg:container relative;
|
||||
@apply pt-8 pb-20 lg:pt-14 lg:pb-[7.5rem] lg:min-h-screen;
|
||||
|
||||
&>div:nth-of-type(1) {
|
||||
@apply flex flex-col md:flex-row justify-between gap-6 max-lg:px-6;
|
||||
|
||||
&>form {
|
||||
@apply w-full md:max-w-[38rem] h-[2.9rem] bg-[#FAFAFA] rounded-[0.6rem] flex items-center pr-4;
|
||||
|
||||
i {
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply h-full px-2 bg-transparent grow outline-none;
|
||||
|
||||
&::placeholder {
|
||||
@apply text-neutral-400 text-lg font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
&>button {
|
||||
@apply h-full;
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-2xl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>aside {
|
||||
@apply max-w-full w-max flex flex-col gap-[1.1rem] lg:gap-12;
|
||||
|
||||
h2 {
|
||||
@apply text-[#333333] text-base lg:text-xl font-bold font-vazir max-lg:pr-2.5;
|
||||
}
|
||||
|
||||
ul {
|
||||
@apply flex justify-around flex-wrap md:flex-col gap-3 p-3 bg-[#FAFAFA] rounded-[0.6rem] overflow-hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-of-type(2) {
|
||||
@apply max-w-full overflow-x-auto left-[20.8rem] right-6 lg:left-[19.3rem] lg:right-0 md:absolute top-full md:top-[5.3rem] lg:pb-4;
|
||||
@apply p-1 mt-[2.6rem];
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@apply max-lg:hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-favorites">
|
||||
<ul>
|
||||
<li v-for="(product, i) in items" :key="i">
|
||||
<ProductCard :data="product" type="main" />
|
||||
</li>
|
||||
</ul>
|
||||
<AppPagination :pages="pages" />
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true }, mobile: { header: { back: true, title: t('favorites') } },
|
||||
})
|
||||
|
||||
const items = ref([])
|
||||
const pages = ref(1)
|
||||
const { meta, query } = useRoute()
|
||||
const { page = 1 } = query
|
||||
|
||||
const { status, data, error } = await useFetch(`/api/users/current/fave/${page}`, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (status.value == 'success') {
|
||||
items.value = data.value.favoriteProducts
|
||||
pages.value = data.value.pages
|
||||
|
||||
} else throw createError(error.value)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-favorites {
|
||||
@apply flex flex-col gap-3 lg:gap-6 justify-center lg:justify-start mx-auto lg:container;
|
||||
@apply pt-8 pb-20 lg:pt-14 lg:pb-[7.5rem] lg:min-h-[60vh];
|
||||
|
||||
&>ul {
|
||||
@apply w-full flex flex-wrap gap-3 lg:gap-6 justify-center lg:justify-start mx-auto lg:container;
|
||||
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.product-card {
|
||||
@apply w-[21.4rem] h-[9.4rem] flex-row shadow-none border-0 rounded-none gap-4 p-0;
|
||||
@apply mt-[1.1rem] border-b border-[#E5E5E5];
|
||||
|
||||
&>img {
|
||||
@apply w-[7.4rem] h-[7.4rem] object-contain object-top self-start;
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply pb-6;
|
||||
|
||||
h2 {
|
||||
@apply mt-0 text-[0.7em];
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-xs;
|
||||
}
|
||||
|
||||
&>button {
|
||||
@apply left-0 bottom-3;
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-xl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-information">
|
||||
<PanelInformation />
|
||||
<PanelBecomeSeller v-if="type == 'BUYER'" />
|
||||
<PanelNationalCard v-if="type == 'RETAILER'" />
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
mobile: {
|
||||
header: {
|
||||
title: t('userInfo'),
|
||||
back: true,
|
||||
options: [
|
||||
{
|
||||
props: {
|
||||
icon: "isax isax-edit-2", severity: "secondary", text: true, rounded: true,
|
||||
click: () => navigateTo('/panel/edit')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
})
|
||||
const { meta } = useRoute()
|
||||
|
||||
if (device.value == 'desktop') navigateTo('/panel/profile')
|
||||
|
||||
const { status, data, error } = await useFetch('/api/users/current', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
const type = ref('')
|
||||
|
||||
if (status.value == 'success') {
|
||||
type.value = data.value.accountType
|
||||
provide('data', data.value)
|
||||
|
||||
}
|
||||
else throw createError(error.value)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-information {
|
||||
@apply flex flex-col px-6 pt-[1.9rem] pb-20 gap-[2.6rem];
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-moeen-factors">
|
||||
<table>
|
||||
<caption>
|
||||
<ul>
|
||||
<li v-for="(value, key) in info" :key="key">
|
||||
<label v-text="$t(key) + ':'" />
|
||||
<span v-text="value" />
|
||||
</li>
|
||||
</ul>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(column, i) in columns" :key="i" v-text="$t(column)" />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr></tr>
|
||||
<tr v-for="(row, i) in rows" :key="i">
|
||||
<td v-text="i + 1" />
|
||||
<td v-for="(value, key) in row" :key="key" v-text="value" />
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true }, mobile: { header: { back: true, title: t('moeenFactors') } },
|
||||
})
|
||||
|
||||
const { meta, query } = useRoute()
|
||||
const { page = 1 } = query
|
||||
|
||||
const info = ref([])
|
||||
const rows = ref([])
|
||||
const columns = ref([])
|
||||
|
||||
const { status, data, error } = await useFetch(`/api/moeens/user`, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
// info.value = data.value.info
|
||||
// rows.value = data.value.items
|
||||
// columns.value = ["row", ...Object.keys(rows.value[0]), ""]
|
||||
} else throw createError(error.value)
|
||||
|
||||
const show = () => { }
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-moeen-factors {
|
||||
@apply container w-full flex flex-col items-center max-lg:px-6 max-lg:gap-4;
|
||||
@apply pt-8 pb-20 lg:pt-14 lg:pb-[7.5rem] overflow-x-auto;
|
||||
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
table {
|
||||
@apply w-[100.3rem] border border-[#E5E5E5];
|
||||
|
||||
caption {
|
||||
@apply h-[3.6rem] border-x border-t px-6 border-[#E5E5E5];
|
||||
|
||||
ul {
|
||||
@apply h-full flex items-center gap-4 text-xs lg:text-base;
|
||||
|
||||
li {
|
||||
@apply flex items-center gap-2 font-vazir whitespace-nowrap;
|
||||
|
||||
label {
|
||||
@apply text-[#333333] font-bold;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-[#7F7F7F] font-medium;
|
||||
}
|
||||
|
||||
&:nth-child(1) {
|
||||
@apply ml-[8.6rem];
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
@apply ml-[8.8rem] mr-auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thead {
|
||||
tr {
|
||||
@apply h-[2.8rem] border border-[#E5E5E5] bg-[#F2F2F2];
|
||||
|
||||
th {
|
||||
@apply border-l border-[#E5E5E5] px-2.5 text-xs lg:text-base;
|
||||
@apply text-center text-[#333333] font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
@apply h-max;
|
||||
|
||||
tr {
|
||||
@apply border-b h-[2.8rem] w-full;
|
||||
|
||||
td {
|
||||
@apply border-l border-[#E5E5E5] px-2.5 text-[0.7em] lg:text-sm;
|
||||
@apply text-center text-zinc-800 font-medium font-vazir;
|
||||
|
||||
&:nth-child(6) {
|
||||
@apply w-[17.2rem] text-right;
|
||||
}
|
||||
|
||||
&:nth-child(12) {
|
||||
@apply w-[3.3rem];
|
||||
|
||||
button {
|
||||
.p-button-icon {
|
||||
@apply text-2xl text-[#B2B2B2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
direction: ltr;
|
||||
@apply ltr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-notifications">
|
||||
<ul>
|
||||
<li v-for="(item, i) in items" :key="i">
|
||||
<PanelNotificationItem :data="item" />
|
||||
</li>
|
||||
</ul>
|
||||
<AppPagination :pages="pages" />
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true }, mobile: { header: { back: true, title: t('notifications') } },
|
||||
})
|
||||
|
||||
const items = ref([])
|
||||
const pages = ref(1)
|
||||
|
||||
const { meta, query } = useRoute()
|
||||
const { page = 1 } = query
|
||||
|
||||
const { status, data, error } = await useFetch(`/api/users/current/notif/${page}`, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
items.value = data.value.notifications
|
||||
pages.value = data.value.pages
|
||||
|
||||
} else throw createError(error.value)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-notifications {
|
||||
@apply container w-full flex flex-col items-center max-lg:px-6 max-lg:gap-4;
|
||||
@apply pt-8 pb-20 lg:pt-14 lg:pb-[7.5rem] max-lg:overflow-hidden;
|
||||
|
||||
&>ul {
|
||||
@apply w-full flex flex-col gap-4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-orders">
|
||||
<ul>
|
||||
<li v-for="(order, i) in items" :key="i">
|
||||
<PanelOrderItem :data="order" />
|
||||
</li>
|
||||
</ul>
|
||||
<AppPagination :pages="pages" />
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true }, mobile: { header: { back: true, title: t('orders') } },
|
||||
})
|
||||
|
||||
const { meta, query } = useRoute()
|
||||
const { page = 1 } = query
|
||||
|
||||
const { status, data, error } = await useFetch(`/api/orders/user/${page}`, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
const items = ref([])
|
||||
const pages = ref(1)
|
||||
|
||||
if (status.value == 'success') {
|
||||
items.value = data.value.items
|
||||
pages.value = data.value.pages
|
||||
|
||||
} else throw createError(error.value)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-orders {
|
||||
@apply w-full lg:container flex flex-col gap-y-10 lg:gap-[2.6rem];
|
||||
@apply px-6 sm:px-4 lg:px-0 pt-8 pb-20 lg:pt-14 lg:pb-[14.9rem];
|
||||
|
||||
&>ul:first-child {
|
||||
@apply flex flex-col gap-x-6 gap-y-10 lg:gap-[2.6rem];
|
||||
}
|
||||
|
||||
.pagination {
|
||||
@apply self-center mt-auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="panel-profile">
|
||||
<PanelProfileCard />
|
||||
<template v-if="device == 'desktop'">
|
||||
<PanelInformation />
|
||||
<PanelBecomeSeller v-if="type == 'BUYER'" />
|
||||
<PanelNationalCard v-else />
|
||||
</template>
|
||||
|
||||
<template v-else-if="device == 'mobile'">
|
||||
<hr />
|
||||
<PanelPorfileMenu />
|
||||
</template>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import init from '../../initialize/panel'
|
||||
const { meta } = useRoute()
|
||||
meta.init = init()
|
||||
provide('init', meta.init)
|
||||
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true }, mobile: { header: false },
|
||||
})
|
||||
|
||||
const type = ref('')
|
||||
const { device } = inject('service')
|
||||
const { status, data, error } = await useFetch('/api/users/current', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
type.value = data.value.accountType
|
||||
meta.confirmToSales = data.value.confirmToSales
|
||||
provide('data', data.value)
|
||||
}
|
||||
else throw createError(error.value)
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.panel-profile {
|
||||
@apply container flex flex-wrap justify-evenly items-center gap-[1.9rem];
|
||||
@apply lg:gap-5 px-6 sm:px-0 pt-8 pb-20 lg:pt-14 lg:pb-[19.3rem];
|
||||
|
||||
hr {
|
||||
@apply bg-[#E5E5E5] w-full;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,186 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="become-seller-form">
|
||||
<h1 v-if="device == 'desktop'">
|
||||
{{ $t("becomeSeller") }}
|
||||
</h1>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="(item, key) in fields" :key="key">
|
||||
<component :id="key" :is="item.is" v-bind="item.props" v-model="form[key]" />
|
||||
<label :for="key"> {{ $t(key) }} </label>
|
||||
<small v-if="errors[key]">{{ errors[key] }}</small>
|
||||
</li>
|
||||
</ul>
|
||||
<div>
|
||||
<Button v-bind="btns.cancel.props" @click="$router.go(-1)" />
|
||||
<Button v-bind="btns.save.props" @click="handleClick()" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import init from '../../../initialize/become-seller'
|
||||
const { btns, fields } = init()
|
||||
|
||||
const { device, toast, t } = inject('service')
|
||||
const config = reactive({
|
||||
mobile: { header: { back: true, title: t('becomeSeller') }, navigation: false },
|
||||
})
|
||||
|
||||
|
||||
const router = useRouter()
|
||||
const errors = ref({})
|
||||
const form = reactive({})
|
||||
|
||||
const { status, data, error } = await useFetch('/api/users/current', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
provide('data', data.value)
|
||||
}
|
||||
else throw createError(error.value)
|
||||
|
||||
const handleClick = async () => {
|
||||
errors.value = {}
|
||||
btns.save.props.loading = true
|
||||
if (form.nationalCard) {
|
||||
const formData = new FormData()
|
||||
formData.append('images', form.nationalCard)
|
||||
|
||||
const upload = await useFetch('/api/images/upload', {
|
||||
headers: { Authorization: useCookie('token') },
|
||||
params: { type: 3 },
|
||||
method: 'post',
|
||||
body: formData,
|
||||
})
|
||||
|
||||
if (upload.status.value == 'success') {
|
||||
const body = Object.assign({}, form)
|
||||
body.nationalCard = upload.data.value.urls[0]
|
||||
|
||||
const { status, error } = await useFetch('/api/users/seller', {
|
||||
headers: { Authorization: useCookie('token') },
|
||||
method: 'put',
|
||||
body
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
toast.add({
|
||||
life: 5000, severity: 'success', summary: t('success'), detail: t('becomeSellerSuccess')
|
||||
})
|
||||
router.push('/panel/profile')
|
||||
} else {
|
||||
if (error.value.statusCode == 422)
|
||||
errors.value = error.value.data
|
||||
else
|
||||
toast.add({
|
||||
life: 5000, severity: 'error', summary: t('error'), detail: error.value.data.message
|
||||
})
|
||||
}
|
||||
} else {
|
||||
toast.add({
|
||||
life: 3000, severity: 'error', summary: t('error'), detail: upload.error.value.message
|
||||
})
|
||||
}
|
||||
} else
|
||||
errors.value.nationalCard = t('noChooseNationalCardImage');
|
||||
|
||||
btns.save.props.loading = false
|
||||
}
|
||||
|
||||
watch(form, () => errors.value = {})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.become-seller-form {
|
||||
@apply w-full flex flex-col items-center gap-12 pb-20 max-lg:pt-9;
|
||||
|
||||
h1 {
|
||||
@apply w-full h-[5.9rem] bg-[#F2F2F2] flex items-center justify-center;
|
||||
@apply text-[#333333] text-2xl font-bold font-iran-sans;
|
||||
}
|
||||
|
||||
&>div {
|
||||
@apply flex flex-col gap-12;
|
||||
|
||||
ul {
|
||||
@apply grid lg:grid-cols-2 gap-6;
|
||||
|
||||
li {
|
||||
@apply w-full sm:w-[27.1rem] h-max relative;
|
||||
|
||||
label {
|
||||
@apply w-max absolute right-3 top-4 px-1 bg-white transition-['top'] select-none;
|
||||
@apply text-neutral-400 text-base font-medium font-vazir cursor-text pointer-events-none;
|
||||
}
|
||||
|
||||
input {
|
||||
@apply h-14;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
@apply w-full rtl rounded-[0.6rem] border shadow-none border-[#CCCCCC] text-zinc-800 font-vazir;
|
||||
}
|
||||
|
||||
.p-inputnumber input {
|
||||
@apply ltr;
|
||||
}
|
||||
|
||||
&:has(img),
|
||||
&:has(.p-inputwrapper-filled),
|
||||
&:has(input:focus),
|
||||
&:has(textarea:focus),
|
||||
&:has(input:not(:placeholder-shown):not([hidden])),
|
||||
&:has(textarea:not(:placeholder-shown)) {
|
||||
label {
|
||||
@apply -top-2 h-4;
|
||||
@apply text-zinc-800 text-xs font-normal font-vazir;
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown,
|
||||
.p-inputnumber,
|
||||
.p-button {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
&:nth-child(7) {
|
||||
@apply row-span-3 h-full;
|
||||
}
|
||||
|
||||
small {
|
||||
@apply text-red-500 font-vazir;
|
||||
}
|
||||
|
||||
&:has(small) * {
|
||||
@apply border-red-500 #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
&>div {
|
||||
@apply inset-x-0 bottom-0 flex gap-3 w-full lg:w-[27.1rem] self-end;
|
||||
@apply max-lg:fixed max-lg:border-t max-lg:shadow-[0_-2px_5px_#0000000a];
|
||||
@apply h-[4.8rem] px-6 pt-3 lg:p-0 lg:h-max bg-white;
|
||||
|
||||
&>button {
|
||||
@apply w-full h-[2.9rem] lg:h-14 #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-sm lg:text-xl font-medium font-vazir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="seller-new-product">
|
||||
<aside v-if="device == 'desktop'">
|
||||
<PanelSellerProfile />
|
||||
<PanelSellerScore />
|
||||
</aside>
|
||||
<section>
|
||||
<div>
|
||||
<template v-for="i in 3" :key="i">
|
||||
<span v-text="i" :class="{ 'active': i == step, 'past': i < step }" />
|
||||
<hr :class="{ 'past': i < step }" />
|
||||
</template>
|
||||
</div>
|
||||
<Transition name="fade" mode="out-in">
|
||||
<PanelSellerStepFirst v-if="step == 1" />
|
||||
<PanelSellerStepSecond v-else-if="step == 2" />
|
||||
<PanelSellerStepThirdy v-else-if="step == 3" />
|
||||
</Transition>
|
||||
</section>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import init from '../../../initialize/new-product.js'
|
||||
provide('init', init())
|
||||
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true, seller: true },
|
||||
mobile: { header: { back: true, toolbar: true, title: t('userPanel') }, navigation: false },
|
||||
})
|
||||
|
||||
const { query } = useRoute()
|
||||
|
||||
const step = ref(1)
|
||||
provide('step', step)
|
||||
|
||||
const { status, data, error } = await useFetch('/api/shop/newProduct', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
},
|
||||
})
|
||||
|
||||
if (status.value == 'success')
|
||||
provide('data', data.value)
|
||||
else throw createError(error.value)
|
||||
|
||||
const form = ref({
|
||||
specs: {
|
||||
[t("weight")]: "",
|
||||
[t("dimensions")]: "",
|
||||
[t("color")]: "",
|
||||
},
|
||||
images: [null]
|
||||
})
|
||||
|
||||
if (query.id) {
|
||||
const { status, data } = await useFetch(`/api/products/id/${query.id}`, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
},
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
form.value = data.value
|
||||
form.value.subCategory =
|
||||
form.value.category.length > 1 ? form.value.category.slice(-1)[0] : null
|
||||
form.value.category =
|
||||
form.value.category.length > 2 ? form.value.category[1] : form.value.category[0]
|
||||
}
|
||||
}
|
||||
|
||||
provide('form', form.value)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-new-product {
|
||||
@apply container flex pt-[1.9rem] pb-4 lg:pt-14 max-lg:px-6 gap-6 relative;
|
||||
|
||||
aside {
|
||||
@apply flex flex-col gap-3 lg:gap-5;
|
||||
}
|
||||
|
||||
&>section {
|
||||
@apply flex flex-col gap-9 grow;
|
||||
|
||||
&>div:not([class]) {
|
||||
@apply w-full h-max flex justify-between items-center gap-2;
|
||||
|
||||
hr {
|
||||
@apply grow h-0.5 bg-[#333333] last:hidden;
|
||||
}
|
||||
|
||||
hr.past {
|
||||
@apply bg-[#47B556];
|
||||
}
|
||||
|
||||
span {
|
||||
@apply w-6 h-6 lg:w-[1.9rem] lg:h-[1.9rem] rounded-full flex justify-center items-center;
|
||||
@apply text-xs lg:text-base font-medium font-vazir border border-[#333333];
|
||||
}
|
||||
|
||||
span.past {
|
||||
@apply text-white bg-[#47B556] border-[#47B556];
|
||||
|
||||
}
|
||||
|
||||
span.active {
|
||||
@apply border-[#47B556] text-[#47B556];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body:has(.seller-new-product) {
|
||||
|
||||
.p-dropdown-panel:has(.p-dropdown-filter-container) {
|
||||
@apply w-[18.3rem] translate-y-3 pb-4 pt-[0.6rem] rounded-[0.6rem];
|
||||
|
||||
.p-dropdown-header {
|
||||
@apply py-0 px-2.5;
|
||||
|
||||
.p-dropdown-filter-container {
|
||||
@apply w-full h-[3.1rem];
|
||||
|
||||
input {
|
||||
@apply m-0 h-full w-full rtl;
|
||||
}
|
||||
|
||||
svg {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown-items-wrapper {
|
||||
@apply h-[19rem] mt-3 ml-3;
|
||||
|
||||
.p-dropdown-item-group {
|
||||
@apply text-[#47B556] text-xs font-medium font-vazir p-2.5;
|
||||
}
|
||||
|
||||
.p-dropdown-item {
|
||||
@apply h-[2.4rem] px-2.5 text-[#333333] text-base font-medium font-vazir;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply bg-[#47B556];
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@apply w-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="seller-orders">
|
||||
<aside v-if="device == 'desktop'">
|
||||
<PanelSellerProfile />
|
||||
<PanelSellerScore />
|
||||
</aside>
|
||||
<ul>
|
||||
<li v-for="(order, i) in orders" :key="i">
|
||||
<PanelSellerOrder :data="order" />
|
||||
</li>
|
||||
<li v-if="orders.length < 1">
|
||||
<p>{{ $t('notFound') }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true, seller: true },
|
||||
mobile: { header: { back: true, toolbar: true, title: t('userPanel') } },
|
||||
})
|
||||
|
||||
const { meta, query } = useRoute()
|
||||
|
||||
const orders = ref([])
|
||||
|
||||
const { status, data, error } = await useFetch('/api/shop/orders', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
orders.value = data.value.shop.pendingProducts
|
||||
provide('data', data.value)
|
||||
}
|
||||
else throw createError(error.value)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-orders {
|
||||
@apply container flex pt-[1.9rem] pb-20 lg:pt-14 max-lg:px-6 gap-6;
|
||||
|
||||
&>aside {
|
||||
@apply flex flex-col gap-3 lg:gap-5;
|
||||
}
|
||||
|
||||
&>ul {
|
||||
@apply w-full flex flex-col gap-3 lg:gap-2.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,294 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="seller-products">
|
||||
<aside v-if="device == 'desktop'">
|
||||
<PanelSellerProfile />
|
||||
<PanelSellerScore />
|
||||
</aside>
|
||||
<DataTable :value="products" :responsiveLayout="rl">
|
||||
<template #header>
|
||||
<div>
|
||||
<IconField>
|
||||
<InputText v-model="form.q" :placeholder="$t('search')" @keypress.enter="filter()" />
|
||||
<InputIcon class="isax isax-search-normal-1" />
|
||||
</IconField>
|
||||
<Dropdown v-model="form.category" :placeholder="$t('selectCategory')" showClear filter
|
||||
:options="categories.items" optionLabel="name" optionGroupLabel="name"
|
||||
optionGroupChildren="sub" optionValue="_id" @change="filter()">
|
||||
<template #dropdownicon>
|
||||
<i class="isax isax-arrow-bottom" />
|
||||
</template>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<span> {{ $t('allProducts') }}: {{ products.length }}</span>
|
||||
</template>
|
||||
<Column v-for="(col, i) in columns[device]" :key="i" v-bind="col">
|
||||
<template #body="{ data, field }">
|
||||
<span>{{ data[field] || field(data) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column>
|
||||
<template #body="{ data, index }">
|
||||
<router-link :to="`/panel/seller/new-product?id=${data.id}`">
|
||||
<Button icon="isax isax-edit" rounded link severity="secondary" />
|
||||
</router-link>
|
||||
<Button icon="isax isax-trash" rounded link severity="secondary" :loading="data.removing"
|
||||
@click="remove(data, index)" />
|
||||
<Button v-if="device == 'mobile'" :label="$t('showDetails')" icon="isax isax-arrow-left-2"
|
||||
rounded link severity="secondary" />
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<template #empty>
|
||||
<p>{{ $t('notFound') }}</p>
|
||||
</template>
|
||||
</DataTable>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t, toast } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true, seller: true },
|
||||
mobile: { header: { back: true, toolbar: true, title: t('userPanel') } },
|
||||
})
|
||||
const router = useRouter()
|
||||
const categories = useCategoriesStore()
|
||||
|
||||
const rl = computed(() => device.value == 'mobile' ? 'stack' : '')
|
||||
const { meta, query, path } = useRoute()
|
||||
|
||||
const products = ref([])
|
||||
|
||||
const columns = reactive({
|
||||
desktop: [
|
||||
{ header: t('row'), field: (p) => products.value.indexOf(p) + 1 },
|
||||
{ header: t('code'), field: 'uid' },
|
||||
{ header: t('productTitle'), field: 'title' },
|
||||
{ header: t('salesPrice'), field: (p) => numberFormat(p.price) },
|
||||
{ header: t('inventory'), field: (p) => numberFormat(p.stockQuantity) },
|
||||
{ header: t('status'), field: (p) => [t('pending'), t('confirmed'), t('rejected')][p.status] },
|
||||
],
|
||||
mobile: [
|
||||
{ field: 'title' },
|
||||
]
|
||||
})
|
||||
|
||||
const { status, data, error } = await useFetch('/api/shop/products', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
},
|
||||
params: query
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
products.value = data.value.shop.products
|
||||
provide('data', data.value)
|
||||
}
|
||||
else throw createError(error.value)
|
||||
|
||||
const form = reactive(Object.assign({}, query))
|
||||
const filter = () => {
|
||||
Object.keys(form).forEach((key) => {
|
||||
if (!form[key]) delete form[key]
|
||||
})
|
||||
router.push({ path, query: form })
|
||||
}
|
||||
|
||||
const remove = async (data, index) => {
|
||||
data.removing = true
|
||||
const { status, error } = await useFetch('/api/products/' + data.id, {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
},
|
||||
method: "delete", body: {}
|
||||
})
|
||||
data.removing = false
|
||||
|
||||
if (status.value == 'success') {
|
||||
products.value.splite(index, 1)
|
||||
} else toast.add({
|
||||
life: 3000, severity: 'error', summary: t('error'), detail: error.value.data.message
|
||||
})
|
||||
}
|
||||
|
||||
const numberFormat = (number) =>
|
||||
new Intl.NumberFormat('fa-IR', { maximumSignificantDigits: 3 }).format(number)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-products {
|
||||
@apply container flex flex-col lg:flex-row pt-[1.9rem] pb-20 lg:pt-14 max-lg:px-6 gap-6;
|
||||
|
||||
aside {
|
||||
@apply flex flex-col gap-3 lg:gap-5;
|
||||
}
|
||||
|
||||
.p-datatable {
|
||||
@apply flex flex-col gap-4 lg:gap-11 rtl grow;
|
||||
|
||||
.p-datatable-header {
|
||||
@apply flex max-lg:flex-col gap-9 lg:items-center bg-transparent p-0 border-none z-[1];
|
||||
|
||||
&>div {
|
||||
@apply flex max-lg:flex-col gap-3 lg:gap-[1.1rem];
|
||||
|
||||
.p-icon-field {
|
||||
@apply z-0 flex items-center w-[21.4rem] h-[2.9rem] lg:w-[29.7rem] lg:h-[3.8rem] rounded-[0.6rem];
|
||||
@apply bg-[#FAFAFA] lg:bg-[#F2F2F2] text-[#4C4C4C] lg:text-[#5D5D5D];
|
||||
@apply text-sm lg:text-lg font-medium font-vazir;
|
||||
|
||||
|
||||
input {
|
||||
@apply rtl w-full h-full border-none bg-transparent shadow-none;
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown {
|
||||
@apply items-center w-[21.4rem] h-[2.9rem] lg:w-[18.3rem] lg:h-[3.8rem] bg-[#FAFAFA] lg:bg-[#F2F2F2] rounded-[0.6rem] border-none;
|
||||
@apply z-[2] #{!important};
|
||||
|
||||
.p-dropdown-label {
|
||||
@apply text-[#4C4C4C] lg:text-[#5D5D5D] text-xs lg:text-base font-medium font-vazir mt-3.5 lg:mt-1.5;
|
||||
}
|
||||
|
||||
i {
|
||||
@apply text-xl lg:text-2xl transition-transform;
|
||||
}
|
||||
|
||||
&.p-overlay-open i {
|
||||
@apply rotate-180;
|
||||
}
|
||||
|
||||
.p-dropdown-clear-icon {
|
||||
@apply left-12 right-auto;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.p-overlay-open)::after {
|
||||
@apply content-['_'] inset-0 fixed bg-black/20 z-[1];
|
||||
}
|
||||
}
|
||||
|
||||
&>span {
|
||||
@apply text-[#333333] text-sm lg:text-xl font-medium font-vazir mr-auto;
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-table {
|
||||
@apply border-separate border-spacing-y-[0.8rem] px-1;
|
||||
|
||||
.p-datatable-thead {
|
||||
@apply w-full h-[3.1rem] max-lg:hidden;
|
||||
|
||||
th {
|
||||
@apply text-[#5D5D5D] text-base font-normal font-vazir bg-[#F2F2F2];
|
||||
@apply first:rounded-r-[0.6rem] last:rounded-l-[0.6rem];
|
||||
}
|
||||
}
|
||||
|
||||
.p-datatable-tbody {
|
||||
@apply lg:translate-y-1.5;
|
||||
|
||||
tr {
|
||||
@apply w-full rounded-[0.6rem] bg-transparent shadow-[0px_0px_6px_#0000001f];
|
||||
|
||||
td {
|
||||
@apply text-[#333333] text-base font-normal font-vazir text-right;
|
||||
@apply first:rounded-r-[0.6rem] last:rounded-l-[0.6rem] py-0 h-[3.8rem] last:text-left;
|
||||
|
||||
&:last-child:not([colspan]) {
|
||||
@apply flex lg:justify-end gap-3 items-center;
|
||||
|
||||
.p-button {
|
||||
@apply p-0 w-max h-max #{!important};
|
||||
|
||||
.p-button-icon {
|
||||
@apply text-lg lg:text-2xl text-[#333333];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&>span {
|
||||
@apply max-lg:py-3 min-h-[3.8rem] flex items-center text-xs lg:text-base text-[#333333] font-vazir;
|
||||
}
|
||||
|
||||
@media(max-width: 1024px) {
|
||||
&>button:last-child {
|
||||
@apply -m-3 mr-auto #{!important};
|
||||
|
||||
.p-button-label {
|
||||
@apply text-xs font-medium font-vazir whitespace-nowrap #{!important};
|
||||
}
|
||||
|
||||
.p-button-icon {
|
||||
@apply mr-px text-[#47B556] #{!important};
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child span {
|
||||
@apply w-full border-b py-3;
|
||||
}
|
||||
|
||||
.p-column-title {
|
||||
@apply hidden #{!important};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
.p-datatable-emptymessage p {
|
||||
@apply lg:h-[3.8rem] flex justify-center items-center
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body:has(.seller-products) {
|
||||
.p-dropdown-panel {
|
||||
@apply w-[18.3rem] h-[19rem] translate-y-3 pb-4 pt-[0.6rem] rounded-[0.6rem];
|
||||
|
||||
.p-dropdown-header {
|
||||
@apply py-0 px-2.5;
|
||||
|
||||
.p-dropdown-filter-container {
|
||||
@apply w-[17rem] h-[3.1rem];
|
||||
|
||||
input {
|
||||
@apply m-0 h-full rtl;
|
||||
}
|
||||
|
||||
svg {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown-items-wrapper {
|
||||
@apply mt-3 ml-3;
|
||||
|
||||
.p-dropdown-item-group {
|
||||
@apply text-[#47B556] text-xs font-medium font-vazir p-2.5;
|
||||
}
|
||||
|
||||
.p-dropdown-item {
|
||||
@apply h-[2.4rem] px-2.5 text-[#333333] text-base font-medium font-vazir;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply bg-[#47B556];
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
@apply w-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<NuxtLayout :name="device" :config="config">
|
||||
<main class="seller-store">
|
||||
<aside>
|
||||
<PanelSellerProfile />
|
||||
<PanelSellerScore />
|
||||
</aside>
|
||||
<section>
|
||||
<div>
|
||||
<PanelSellerMangement title="warehouse" />
|
||||
<PanelSellerMangement title="orders" />
|
||||
<img src="/images/sell.svg">
|
||||
</div>
|
||||
<PanelSellerUpcoming />
|
||||
<div>
|
||||
<PanelSellerSales title="status" />
|
||||
<PanelSellerSales title="number" />
|
||||
<PanelSellerPieChart />
|
||||
</div>
|
||||
<PanelSellerBestSelling />
|
||||
</section>
|
||||
</main>
|
||||
</NuxtLayout>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const { device, t } = inject('service')
|
||||
const config = reactive({
|
||||
desktop: { toolbar: true, seller: true },
|
||||
mobile: { header: { back: true, toolbar: true, title: t('userPanel') } },
|
||||
})
|
||||
|
||||
const { meta, query } = useRoute()
|
||||
|
||||
const { status, data, error } = await useFetch('/api/shop', {
|
||||
headers: {
|
||||
Authorization: useCookie('token')
|
||||
}
|
||||
})
|
||||
|
||||
if (status.value == 'success') {
|
||||
provide('data', data.value)
|
||||
}
|
||||
else throw createError(error.value)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.seller-store {
|
||||
@apply w-full container flex flex-col lg:flex-row pt-[1.9rem] pb-20 lg:pt-14 max-lg:px-6 gap-6 justify-center;
|
||||
|
||||
aside {
|
||||
@apply flex max-lg:flex-wrap max-lg:justify-between lg:flex-col gap-3 lg:gap-5;
|
||||
}
|
||||
|
||||
&>section {
|
||||
@apply flex flex-col gap-6 lg:gap-[1.9rem];
|
||||
|
||||
&>div:nth-child(1) {
|
||||
@apply flex max-lg:flex-wrap gap-3 max-lg:justify-between lg:gap-6;
|
||||
|
||||
img {
|
||||
@apply object-cover object-[0%_75%] w-[21.4rem] h-[18.1rem] lg:w-[18.3rem] lg:h-[25.2rem] rounded-[0.6rem];
|
||||
@apply max-xl:hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&>div:nth-child(3) {
|
||||
@apply flex max-lg:flex-wrap gap-3 max-lg:justify-between lg:gap-6;
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user