complete ticket section
This commit is contained in:
Vendored
+1
@@ -41,6 +41,7 @@ declare module 'vue' {
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SecondStep: typeof import('./src/components/ProductFormSteps/SecondStep.vue')['default']
|
||||
Sidebar: typeof import('primevue/sidebar')['default']
|
||||
Tag: typeof import('primevue/tag')['default']
|
||||
Textarea: typeof import('primevue/textarea')['default']
|
||||
TheWelcome: typeof import('./src/components/TheWelcome.vue')['default']
|
||||
ThirdStep: typeof import('./src/components/ProductFormSteps/ThirdStep.vue')['default']
|
||||
|
||||
@@ -106,7 +106,7 @@ const items = ref([
|
||||
{
|
||||
label: t('addVideo'),
|
||||
icon: 'pi pi-file-edit',
|
||||
command: () => router.push('/courses/update')
|
||||
command: () => router.push('/videos')
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
+20
-5
@@ -53,11 +53,11 @@ const router = createRouter({
|
||||
// name: 'Orders',
|
||||
// component: () => import('@/views/Orders.vue'),
|
||||
// },
|
||||
// {
|
||||
// path: '/comments',
|
||||
// name: 'Comments',
|
||||
// component: () => import('@/views/Comments.vue'),
|
||||
// },
|
||||
{
|
||||
path: '/comments',
|
||||
name: 'Comments',
|
||||
component: () => import('@/views/Comments.vue'),
|
||||
},
|
||||
{
|
||||
path: '/users',
|
||||
name: 'Users',
|
||||
@@ -83,6 +83,11 @@ const router = createRouter({
|
||||
name: 'EditCoupon',
|
||||
component: () => import('@/views/EditCoupon.vue'),
|
||||
},
|
||||
{
|
||||
path: '/comments/:id',
|
||||
name: 'Answer',
|
||||
component: () => import('@/views/AnswerComment.vue'),
|
||||
},
|
||||
{
|
||||
path: '/tickets/:id',
|
||||
name: 'ShowTickets',
|
||||
@@ -93,6 +98,16 @@ const router = createRouter({
|
||||
name: 'Tickets',
|
||||
component: () => import('@/views/Tickets.vue'),
|
||||
},
|
||||
{
|
||||
path: '/videos',
|
||||
name: 'Videos',
|
||||
component: () => import('@/views/Videos.vue'),
|
||||
},
|
||||
{
|
||||
path: '/videos/:id',
|
||||
name: 'AddVideos',
|
||||
component: () => import('@/views/AddVideos.vue'),
|
||||
},
|
||||
{
|
||||
path: '/pages',
|
||||
children: [
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import axios from '../plugins/axios'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useHeadlinesStore = defineStore('headlines', {
|
||||
state: () => ({
|
||||
items: [],
|
||||
brands: [],
|
||||
categories: [],
|
||||
fetching: true,
|
||||
total: 0
|
||||
}),
|
||||
actions: {
|
||||
async index(courseId) {
|
||||
this.items = [];
|
||||
this.fetching = true
|
||||
const { status, data } = await axios.get('/courseHeadline', courseId)
|
||||
this.fetching = false
|
||||
return data
|
||||
|
||||
},
|
||||
|
||||
async create(newCourse) {
|
||||
const { status, data } = await axios.post('/course', newCourse)
|
||||
|
||||
return data
|
||||
|
||||
},
|
||||
async delete(id) {
|
||||
const { status, data } = await axios.delete(`/course/${id}`, id)
|
||||
|
||||
return data
|
||||
|
||||
},
|
||||
|
||||
async edit(id) {
|
||||
const { status, data } = await axios.get(`/course/${id}`)
|
||||
return data
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
// async store(form) {
|
||||
// const result = await axios.post('/admin/products/add', form)
|
||||
|
||||
// if (result.status === 201)
|
||||
// this.items.unshift(result.data)
|
||||
|
||||
// return result;
|
||||
// },
|
||||
// async update(id, form) {
|
||||
// const result = await axios.put(`/admin/products/${id}`, form)
|
||||
|
||||
// if (result.status === 200) {
|
||||
// const index = this.items.findIndex(({ _id }) => _id == id)
|
||||
// this.items[index] = result.data
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// },
|
||||
// async confirm(id, form) {
|
||||
// const result = await axios.post(`/admin/products/confirm/${id}`, form)
|
||||
|
||||
// if (result.status === 200) {
|
||||
// const index = this.items.findIndex(({ _id }) => _id == id)
|
||||
// this.items[index] = result.data
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// },
|
||||
// async remove(id) {
|
||||
// const result = await axios.delete(`/admin/products/${id}`)
|
||||
|
||||
// if (result.status === 200) {
|
||||
// const index = this.items.findIndex(({ _id }) => _id == id)
|
||||
// this.items.splice(index, 1)
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// },
|
||||
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,178 @@
|
||||
|
||||
import axios from '../plugins/axios'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCommentStore = defineStore('comments', {
|
||||
state: () => ({
|
||||
items: [],
|
||||
fetching: true,
|
||||
total: 0
|
||||
}),
|
||||
getters: {
|
||||
// treeSelect: (state) => (id = null) => {
|
||||
// if (id) {
|
||||
// const foreach = (items) => {
|
||||
// return items.filter((item) => {
|
||||
// if (item.key != id) {
|
||||
// if (item.children)
|
||||
// item.children = foreach(item.children)
|
||||
// return true
|
||||
// } else return false
|
||||
// })
|
||||
// }
|
||||
// return foreach(state.items)
|
||||
// }
|
||||
// return state.items
|
||||
// },
|
||||
find: (state) => (id) => {
|
||||
if (id) {
|
||||
const foreach = (items) => {
|
||||
const item = items.find(({ key }) => key == id)
|
||||
if (item) return item
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
|
||||
if (item.children) {
|
||||
const temp = foreach(item.children)
|
||||
if (temp) return temp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foreach(state.items)
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async reply(id, body) {
|
||||
this.fetching = true
|
||||
const data = await axios.post(`/course/${id}/comment`, body)
|
||||
this.fetching = false
|
||||
console.log(id, 'id');
|
||||
return data
|
||||
|
||||
|
||||
if (status === 200) {
|
||||
const map = (items, parent = null) => {
|
||||
return items.map((item) => {
|
||||
const temp = {
|
||||
key: item._id,
|
||||
label: item.name,
|
||||
data: item
|
||||
}
|
||||
if (parent)
|
||||
temp.data.parentCategoryID = { [parent]: true }
|
||||
if (item.sub) {
|
||||
temp.children = map(item.sub, temp.key)
|
||||
delete temp.data.sub
|
||||
}
|
||||
return temp;
|
||||
});
|
||||
}
|
||||
|
||||
this.items = map(data)
|
||||
|
||||
// this.items = data.items
|
||||
// this.total = data.total
|
||||
}
|
||||
},
|
||||
async ticket(id) {
|
||||
this.fetching = true
|
||||
const { status, data } = await axios.get(`/ticket/${id}`)
|
||||
this.fetching = false
|
||||
return data
|
||||
if (status === 200) {
|
||||
const map = (items, parent = null) => {
|
||||
return items.map((item) => {
|
||||
const temp = {
|
||||
key: item._id,
|
||||
label: item.name,
|
||||
data: item
|
||||
}
|
||||
if (parent)
|
||||
temp.data.parentCategoryID = { [parent]: true }
|
||||
if (item.sub) {
|
||||
temp.children = map(item.sub, temp.key)
|
||||
delete temp.data.sub
|
||||
}
|
||||
return temp;
|
||||
});
|
||||
}
|
||||
|
||||
this.items = map(data)
|
||||
|
||||
// this.items = data.items
|
||||
// this.total = data.total
|
||||
}
|
||||
},
|
||||
async close(id){
|
||||
this.fetching = true
|
||||
const { status, data } = await axios.patch(`/ticket/close/${id}`, id)
|
||||
this.fetching = false
|
||||
return data
|
||||
if (status === 200) {
|
||||
const map = (items, parent = null) => {
|
||||
return items.map((item) => {
|
||||
const temp = {
|
||||
key: item._id,
|
||||
label: item.name,
|
||||
data: item
|
||||
}
|
||||
if (parent)
|
||||
temp.data.parentCategoryID = { [parent]: true }
|
||||
if (item.sub) {
|
||||
temp.children = map(item.sub, temp.key)
|
||||
delete temp.data.sub
|
||||
}
|
||||
return temp;
|
||||
});
|
||||
}
|
||||
|
||||
this.items = map(data)
|
||||
|
||||
// this.items = data.items
|
||||
// this.total = data.total
|
||||
}
|
||||
},
|
||||
|
||||
async create(id) {
|
||||
const result = await axios.post('/courseCategory', {
|
||||
name: id.value
|
||||
})
|
||||
|
||||
//console.log('result', result);
|
||||
return result;
|
||||
},
|
||||
|
||||
async store(form) {
|
||||
const result = await axios.post('/admin/categories', form)
|
||||
|
||||
if (result.status === 201)
|
||||
this.items.unshift(result.data)
|
||||
|
||||
return result;
|
||||
},
|
||||
async update(id, form) {
|
||||
const result = await axios.put(`/admin/categories/${id}`, form)
|
||||
|
||||
if (result.status === 200) {
|
||||
const index = this.items.findIndex(({ _id }) => _id == id)
|
||||
this.items[index] = result.data
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
async remove(id) {
|
||||
const result = await axios.delete(`/courseCategory/${id}`)
|
||||
|
||||
if (result.status === 200) {
|
||||
const index = this.items.findIndex(({ _id }) => _id == id)
|
||||
this.items.splice(index, 1)
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -57,17 +57,6 @@ export const useStaticsStore = defineStore('statics', {
|
||||
},
|
||||
async edit(data) {
|
||||
this.fetching = true
|
||||
// const json = {
|
||||
// "about": "",
|
||||
// "student": 100,
|
||||
// "hours": 0,
|
||||
// "title": 0,
|
||||
// "expertise": 0,
|
||||
// "nameCompany": "string",
|
||||
// "mission": "string",
|
||||
// "start": "string",
|
||||
// "goals": "string"
|
||||
// }
|
||||
const result = await axios.patch('/admin/settings/edit', data)
|
||||
this.fetching = false
|
||||
return result
|
||||
|
||||
+8
-18
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<FloatLabel>
|
||||
<InputText class="w-full" v-model="data.videosCount" />
|
||||
<InputText class="w-full" v-model="data.hours" />
|
||||
<label>{{ $t("تعداد ساعات آموزشی") }}</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
@@ -57,24 +57,13 @@
|
||||
import { useStaticsStore } from "@/stores/statics";
|
||||
import { useRoute } from "vue-router";
|
||||
const data = reactive({
|
||||
about: "string",
|
||||
student: 0,
|
||||
hours: 0,
|
||||
title: 0,
|
||||
expertise: 0,
|
||||
nameCompany: "string",
|
||||
mission: "string",
|
||||
start: "string",
|
||||
goals: "string",
|
||||
// description: "",
|
||||
// videosCount: null,
|
||||
// rates: null,
|
||||
student: 0,
|
||||
title: 0,
|
||||
hours: 0,
|
||||
});
|
||||
const save = async (data) => {
|
||||
console.log('form', data);
|
||||
console.log('json', JSON.stringify(data));
|
||||
const { result } = await store.edit(data);
|
||||
console.log("res", result);
|
||||
const result = await store.edit(data);
|
||||
};
|
||||
const expandedKeys = ref({});
|
||||
|
||||
@@ -88,9 +77,10 @@
|
||||
|
||||
watchEffect(async () => {
|
||||
await store.index();
|
||||
// console.log(store.items);
|
||||
data.expertise = store.items.expertise;
|
||||
data.student = store.items.student;
|
||||
//data.videosCount = store.items.hours;
|
||||
data.title = store.items.title;
|
||||
data.hours = store.items.hours;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<h2>add</h2>
|
||||
{{ videos }}
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, watchEffect } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { useHeadlinesStore } from "@/stores/Headlines"
|
||||
const store = useHeadlinesStore()
|
||||
const route = useRoute()
|
||||
const videos = ref([])
|
||||
|
||||
watchEffect(async() => {
|
||||
videos.value = await store.index({
|
||||
courseId: route.params.id
|
||||
})
|
||||
console.log(videos);
|
||||
})
|
||||
|
||||
</script>
|
||||
+61
-23
@@ -1,31 +1,69 @@
|
||||
<template>
|
||||
<Panel :header="$t('admins')">
|
||||
<DataTable :value="admins">
|
||||
<Column class="w-16">
|
||||
<template #body="{ data: { avatarMedia } }">
|
||||
<Avatar
|
||||
:image="avatarMedia"
|
||||
size="large"
|
||||
shape="circle"
|
||||
class="transition-transform hover:scale-150"
|
||||
/>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="name" :header="$t('name')" />
|
||||
<Column field="id" :header="$t('id')" />
|
||||
<Column field="phone" :header="$t('phoneNumber')" />
|
||||
<Column field="statusText" :header="$t('status')" />
|
||||
<Column field="userName" :header="$t('username')" />
|
||||
<Column headerClass="w-32">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
icon="pi pi-eye"
|
||||
rounded
|
||||
text
|
||||
severity="secondary"
|
||||
@click="show(data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<template #empty>
|
||||
<p>{{ store.fetching ? $t("loading") : $t("emptyTable") }}</p>
|
||||
</template>
|
||||
|
||||
ادمین ها
|
||||
|
||||
<input id="avatar" ref="files" name="file" type="file" @change="change">
|
||||
<template #footer>
|
||||
<!--
|
||||
<Paginator :rows="store.total" :totalRecords="store.total" :rowsPerPageOptions="[5, 20, 30]"></Paginator>
|
||||
-->
|
||||
|
||||
<Pagination :total="store.total" />
|
||||
</template>
|
||||
</DataTable>
|
||||
</Panel>
|
||||
</template>
|
||||
<script setup>
|
||||
import { useUsersStore } from "@/stores/users";
|
||||
import { inject, reactive, defineAsyncComponent, ref, watchEffect } from "vue";
|
||||
const store = useUsersStore();
|
||||
const admins = ref()
|
||||
|
||||
import { useUploaderStore } from "@/stores/uploader";
|
||||
const avatar = document.querySelector("#avatar");
|
||||
const store = useUploaderStore()
|
||||
//const files = ref(null)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const change = async (avatar) => {
|
||||
|
||||
const data = await store.uploader(avatar.target.value)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const UserDetails = defineAsyncComponent(() =>
|
||||
import("../components/UserDetails.vue")
|
||||
);
|
||||
const { dialog, t } = inject("service");
|
||||
|
||||
const show = (user) => {
|
||||
dialog.open(UserDetails, {
|
||||
props: { modal: true, closable: true, header: t("userDetails") },
|
||||
data: { user, confirm: store.confirm },
|
||||
});
|
||||
};
|
||||
watchEffect(async () => {
|
||||
await store.index(1, 100);
|
||||
console.log("user", store.items);
|
||||
admins.value = store.items.filter(item => item.roles[1] === 'Admin' );
|
||||
console.log('filter',admins);
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,125 @@
|
||||
<template>
|
||||
<Panel :header="$t('پاسخ به نظرات')">
|
||||
<div v-for="item in data.comments" :key="item.id">
|
||||
<div class="card mb-8">
|
||||
<Panel toggleable>
|
||||
<template #header>
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar :image="item.createdBy.avatarMedia" size="large" shape="circle" />
|
||||
<span class="font-bold">{{item.createdBy.name}}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button icon="pi pi-reply" rounded text @click="reply(item.id)"></Button>
|
||||
<Tag v-if="item.replies.length === 0" icon="pi pi-times" class="gap-1" severity="danger" value="در انتظار پاسخ"></Tag>
|
||||
<Tag v-else icon="pi pi-check" severity="success" class="gap-1" value="پاسخ داده شده"></Tag>
|
||||
</div>
|
||||
<span class="text-surface-500 dark:text-surface-400">{{ jDate(item.createdAt) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #icons>
|
||||
<Menu ref="menu" id="config_menu" :model="items" popup />
|
||||
</template>
|
||||
<p class="m-0">
|
||||
{{ item.text }}
|
||||
</p>
|
||||
<p v-for="answr in item.replies" :key="answr.id" class="border rounded-md ms-[60px] mt-2 p-2">{{answr.text}}</p>
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<Textarea v-model="value" placeholder="پاسخ..." class="w-full" rows="5" />
|
||||
<div class="flex pt-4 justify-end">
|
||||
<Button
|
||||
:label="$t('send')"
|
||||
icon="pi pi-send"
|
||||
severity="success"
|
||||
:loading="saving"
|
||||
@click="sendReply"
|
||||
/>
|
||||
</div>
|
||||
</Panel>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script setup>
|
||||
import { useToast } from "primevue/usetoast";
|
||||
import { useRouter } from 'vue-router';
|
||||
import Menu from 'primevue/menu';
|
||||
import {jDate} from "@/utils/jDate";
|
||||
import { ref, watchEffect } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import {useProductsStore} from "@/stores/products";
|
||||
import {useCommentStore} from "@/stores/courseComments"
|
||||
const store = useProductsStore()
|
||||
const commentStore = useCommentStore()
|
||||
const route = useRoute()
|
||||
const data = ref([])
|
||||
|
||||
const menu = ref(null);
|
||||
const toast = useToast();
|
||||
const router = useRouter();
|
||||
const replyTarget = ref(null)
|
||||
const value = ref(null)
|
||||
const reply = (id) =>{
|
||||
replyTarget.value = id
|
||||
}
|
||||
const sendReply = async() =>{
|
||||
if (replyTarget.value === null || replyTarget.value === '') {
|
||||
return console.log('کامنت مورد نظر را انتخاب کنید');
|
||||
}
|
||||
if (value.value === null || value.value === '') {
|
||||
return console.log('متن پیام را وارد کنید');
|
||||
}
|
||||
// console.log(value.value);
|
||||
const rep = await commentStore.reply(route.params.id, {
|
||||
text: value.value,
|
||||
parent: replyTarget.value
|
||||
})
|
||||
console.log('rep', rep);
|
||||
}
|
||||
const items = ref([
|
||||
{
|
||||
label: 'Refresh',
|
||||
icon: 'pi pi-refresh'
|
||||
},
|
||||
{
|
||||
label: 'Search',
|
||||
icon: 'pi pi-search'
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: 'pi pi-times'
|
||||
}
|
||||
]);
|
||||
|
||||
const toggle = (event) => {
|
||||
menu.value.toggle(event);
|
||||
};
|
||||
|
||||
const save = () => {
|
||||
toast.add({ severity: 'success', summary: 'Success', detail: 'Data Saved', life: 3000 });
|
||||
};
|
||||
|
||||
watchEffect(async() => {
|
||||
data.value = await store.edit(route.params.id)
|
||||
console.log('store', data.value.comments);
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<Panel :header="$t('comments')">
|
||||
<DataTable :value="store.items">
|
||||
<Column class="w-16" :header="$t('image')">
|
||||
<template #body="{ data: { image } }">
|
||||
<Avatar
|
||||
:image="image"
|
||||
size="large"
|
||||
shape="circle"
|
||||
class="transition-transform hover:scale-150"
|
||||
/>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="name" :header="$t('name')" />
|
||||
<Column
|
||||
:field="({ categories }) => categories[0].name"
|
||||
:header="$t('category')"
|
||||
/>
|
||||
|
||||
<Column field="price" :header="$t('price')" />
|
||||
<Column
|
||||
:field="({ createdAt }) => jDate(createdAt)"
|
||||
:header="$t('createdAt')"
|
||||
class="w-28"
|
||||
/>
|
||||
<Column
|
||||
:field="({ updatedAt }) => jDate(updatedAt)"
|
||||
:header="$t('updatedAt')"
|
||||
class="w-32 text-center"
|
||||
/>
|
||||
<Column field="averageRating" :header="$t('امتیاز')" />
|
||||
|
||||
<Column :header="$t('مشاهده نظرات')" headerClass="w-32">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-end">
|
||||
<Button icon="pi pi-eye" rounded text severity="secondary" @click="show(data)" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<!-- <Column :field="({ price }) => numberFormat(price)" :header="$t('price')" />
|
||||
<Column :header="$t('category')" class="whitespace-nowrap">
|
||||
<template #body="{ data: { category } }">
|
||||
<span v-if="category.length > 0">
|
||||
{{ category.slice(-1)[0].name }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="name" :header="$t('reviews')">
|
||||
<template #body="{ data: { name } }">
|
||||
<Rating :modelValue="name" readonly :cancel="false" class="[&_svg]:text-yellow-400" />
|
||||
</template>
|
||||
</Column>
|
||||
<Column :header="$t('status')">
|
||||
<template #body="{ data: { confirmToShow, _id } }">
|
||||
<Tag v-if="!confirmToShow" value="غیر فعال" severity="danger" @click="accepter(_id,true)"/>
|
||||
<Tag v-if="confirmToShow" value="فعال" severity="success" @click="accepter(_id,false)"/>
|
||||
</template>
|
||||
|
||||
</Column>
|
||||
<Column :field="({ createdAt }) => jDate(createdAt)" :header="$t('createdAt')" class="w-28" />
|
||||
<Column :field="({ updatedAt }) => jDate(updatedAt)" :header="$t('updatedAt')" class="w-32 text-center" />
|
||||
<Column headerClass="w-32">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-end">
|
||||
<Button icon="pi pi-eye" rounded text severity="secondary" @click="show(data)" />
|
||||
<Button icon="pi pi-pencil" rounded text severity="secondary" @click="edit(data)" />
|
||||
<Button icon="pi pi-trash" rounded text severity="danger" :loading="data.removing"
|
||||
@click="remove(data)" />
|
||||
</div>
|
||||
</template>
|
||||
</Column> -->
|
||||
<template #empty>
|
||||
<p>{{ store.fetching ? $t("loading") : $t("emptyTable") }}</p>
|
||||
</template>
|
||||
<template #footer>
|
||||
<Pagination :total="store.total" />
|
||||
</template>
|
||||
</DataTable>
|
||||
<Galleria
|
||||
v-model:visible="galleryVes"
|
||||
:value="gallery"
|
||||
:numVisible="5"
|
||||
containerClass="w-1/2"
|
||||
circular
|
||||
fullScreen
|
||||
showItemNavigators
|
||||
:showThumbnailNavigators="false"
|
||||
>
|
||||
<template #item="{ item: { itemImageSrc, alt } }">
|
||||
<img :src="itemImageSrc" :alt="alt" class="w-full block" />
|
||||
</template>
|
||||
</Galleria>
|
||||
<Column headerClass="w-32">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
icon="pi pi-eye"
|
||||
rounded
|
||||
text
|
||||
severity="secondary"
|
||||
@click="show(data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useProductsStore } from '@/stores/products';
|
||||
import { jDate } from '@/utils/jDate';
|
||||
import { numberFormat } from '@/utils/numberFormat';
|
||||
import { defineAsyncComponent, inject, onBeforeMount, ref, watchEffect } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const ProductForm = defineAsyncComponent(() => import('../components/ProductForm.vue'));
|
||||
const ProductDetails = defineAsyncComponent(() => import('../components/ProductDetails.vue'));
|
||||
|
||||
const { toast, dialog, confirm, t } = inject('service')
|
||||
|
||||
var gallery = []
|
||||
const galleryVes = ref(false)
|
||||
const openGallery = (images =>{
|
||||
gallery = []
|
||||
images.map(image =>{
|
||||
gallery.push({itemImageSrc:image, alt:"product"})
|
||||
})
|
||||
galleryVes.value = true
|
||||
|
||||
})
|
||||
const router = useRouter()
|
||||
const show = (data) => {
|
||||
console.log(data);
|
||||
router.push(`/comments/${data._id}`)
|
||||
}
|
||||
const UserDetails = defineAsyncComponent(() => import('../components/UserDetails.vue'));
|
||||
|
||||
const store = useProductsStore()
|
||||
const route = useRoute()
|
||||
const edit = (data) =>{
|
||||
router.push(data._id)
|
||||
}
|
||||
|
||||
|
||||
const remove = (data) => {
|
||||
console.log('done', data._id)
|
||||
confirm.require({
|
||||
message: t('Are you sure you want to proceed?'),
|
||||
header: t('Danger Zone'),
|
||||
icon: 'pi pi-info-circle',
|
||||
acceptClass: 'p-button-danger p-button-sm',
|
||||
rejectClass: 'p-button-secondary p-button-outlined p-button-sm',
|
||||
rejectLabel: t('cancel'),
|
||||
acceptLabel: t('beOmitted'),
|
||||
defaultFocus: 'reject',
|
||||
accept: async () => {
|
||||
const deleted = await store.delete(data._id)
|
||||
console.log(deleted);
|
||||
await store.index()
|
||||
}
|
||||
// product.removing = true
|
||||
|
||||
// const { status, data } = await store.remove(product._id);
|
||||
|
||||
// product.removing = false
|
||||
|
||||
// if (status === 200)
|
||||
// toast.add({
|
||||
// severity: 'success', summary: t('successful'), detail: t('destroySuccessfully'), life: 3000
|
||||
// });
|
||||
// else
|
||||
// toast.add({
|
||||
// severity: 'error', summary: t('error'), detail: data.msg, life: 3000
|
||||
// });
|
||||
|
||||
// },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
watchEffect(async() => {
|
||||
const { page = 1, pageSize = 10 } = route.query
|
||||
await store.index(page, pageSize)
|
||||
console.log(store.items[2].image);
|
||||
console.log(String.fromCharCode(store.items[2].image)).join('');
|
||||
// store.items.forEach((product) => {
|
||||
// product.status = { value: t('active'), severity: 'success' };
|
||||
// })
|
||||
|
||||
})
|
||||
|
||||
const accepter = async (id,confirmToShow)=>{
|
||||
const { status, data } = await store.confirm(id, {confirmToShow});
|
||||
|
||||
|
||||
if (status === 200)
|
||||
toast.add({
|
||||
severity: 'success', summary: t('successful'), detail: t('destroySuccessfully'), life: 3000
|
||||
});
|
||||
else
|
||||
toast.add({
|
||||
severity: 'error', summary: t('error'), detail: data.msg, life: 3000
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
+10
-14
@@ -24,7 +24,7 @@
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<FloatLabel>
|
||||
<InputText class="w-full" v-model="data.videosCount" />
|
||||
<InputText class="w-full" v-model="data.expertise" />
|
||||
<label>{{ $t("تعداد ویدیو های آموزشی") }}</label>
|
||||
</FloatLabel>
|
||||
</div>
|
||||
@@ -80,24 +80,17 @@ import {
|
||||
import { useStaticsStore } from "@/stores/statics";
|
||||
import { useRoute } from "vue-router";
|
||||
const data = reactive({
|
||||
about: "string",
|
||||
student: 0,
|
||||
hours: 0,
|
||||
title: 0,
|
||||
expertise: 0,
|
||||
nameCompany: "string",
|
||||
mission: "string",
|
||||
start: "string",
|
||||
goals: "string",
|
||||
// description: "",
|
||||
// videosCount: null,
|
||||
// rates: null,
|
||||
about: "string",
|
||||
|
||||
});
|
||||
const save = async (data) => {
|
||||
console.log('form', data);
|
||||
console.log('json', JSON.stringify(data));
|
||||
const { result } = await store.edit(data);
|
||||
console.log("res", result);
|
||||
// console.log('form', data);
|
||||
// console.log('json', JSON.stringify(data));
|
||||
const result = await store.edit(data);
|
||||
// console.log("res", result);
|
||||
};
|
||||
const expandedKeys = ref({});
|
||||
|
||||
@@ -113,6 +106,9 @@ watchEffect(async () => {
|
||||
await store.index();
|
||||
// console.log(store.items);
|
||||
data.student = store.items.student;
|
||||
data.title = store.items.title;
|
||||
data.expertise = store.items.expertise;
|
||||
data.about = store.items.about;
|
||||
//data.videosCount = store.items.hours;
|
||||
});
|
||||
</script>
|
||||
|
||||
+22
-26
@@ -1,35 +1,30 @@
|
||||
<template>
|
||||
<Panel :header="$t('گفتگو')">
|
||||
<!-- <DataTable :value="messages.ticketMessages">
|
||||
<Column field="content" :header="$t('متن تیکت')" />
|
||||
<Column field="id" :header="$t('id')" />
|
||||
<Column :field="({createdAt}) => jDate(createdAt)" :header="$t('createdAt')" />
|
||||
<Column :header="$t('options')" headerClass="w-32">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
icon="pi pi-reply"
|
||||
rounded
|
||||
text
|
||||
severity="secondary"
|
||||
@click="edit(data)"
|
||||
/>
|
||||
</div></template></Column>
|
||||
<template #empty>
|
||||
<p>{{ store.fetching ? $t("loading") : $t("emptyTable") }}</p>
|
||||
<div v-for="item in messages" :key="item.id" >
|
||||
<div class="card mb-8">
|
||||
<Panel toggleable>
|
||||
<template #header >
|
||||
<div class="flex items-center gap-2">
|
||||
<Avatar :image="item.createdBy.avatarMedia" size="large" shape="circle" />
|
||||
<span class="font-bold">{{item.createdBy.name}}</span>
|
||||
<Tag v-if="item.createdBy.roles[0] === 'Admin'" class="gap-1" icon="pi pi-user" severity="info" value="ادمین"></Tag>
|
||||
<Tag v-else class="gap-1" icon="pi pi-user" severity="success" value="کاربر"></Tag>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<div class="flex flex-wrap items-center justify-end">
|
||||
|
||||
<span class="text-surface-500 dark:text-surface-400">{{ jDate(item.createdAt) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</DataTable> -->
|
||||
|
||||
<div v-for="item in messages.ticketMessages" :key="item.id">
|
||||
<!-- <Textarea :value="item.content" cols="30" disabled /> -->
|
||||
<Fieldset legend="متن تیکت" class="mb-8" :class="{'bg-gray-100' : item.createdBy.roles[0] === 'Admin'}">
|
||||
<p class="m-0">
|
||||
{{ item.content }}
|
||||
</p>
|
||||
</Fieldset>
|
||||
</Panel>
|
||||
</div>
|
||||
<Editor v-model="value" editorStyle="height: 320px" />
|
||||
|
||||
</div>
|
||||
<Textarea v-model="value" placeholder="پاسخ..." class="w-full" rows="5" />
|
||||
<div class="flex pt-4 justify-end">
|
||||
<Button
|
||||
:label="$t('send')"
|
||||
@@ -61,8 +56,9 @@ const send = async (value) => {
|
||||
console.log('sended', sended);
|
||||
};
|
||||
watchEffect(async () => {
|
||||
messages.value = await store.ticket(route.params.id);
|
||||
console.log("mess", messages.value.ticketMessages);
|
||||
const data = await store.ticket(route.params.id);
|
||||
messages.value = data.ticketMessages.reverse()
|
||||
console.log("mess", data);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<Panel :header="$t('مدیریت ویدیو ها')">
|
||||
|
||||
<DataTable :value="store.items">
|
||||
<Column class="w-16" :header="$t('image')">
|
||||
<template #body="{ data: { image } }">
|
||||
<Avatar
|
||||
:image="image"
|
||||
size="large"
|
||||
shape="circle"
|
||||
class="transition-transform hover:scale-150"
|
||||
/>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="name" :header="$t('name')" />
|
||||
<Column
|
||||
:field="({ categories }) => categories[0].name"
|
||||
:header="$t('category')"
|
||||
/>
|
||||
|
||||
<Column field="price" :header="$t('price')" />
|
||||
<Column
|
||||
:field="({ createdAt }) => jDate(createdAt)"
|
||||
:header="$t('createdAt')"
|
||||
class="w-28"
|
||||
/>
|
||||
<Column
|
||||
:field="({ updatedAt }) => jDate(updatedAt)"
|
||||
:header="$t('updatedAt')"
|
||||
class="w-32 text-center"
|
||||
/>
|
||||
<Column field="averageRating" :header="$t('امتیاز')" />
|
||||
|
||||
<Column :header="$t('options')" headerClass="w-32">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-end">
|
||||
<Button icon="pi pi-eye" rounded text severity="secondary" @click="show(data)" />
|
||||
<Button
|
||||
icon="pi pi-pencil"
|
||||
rounded
|
||||
text
|
||||
severity="secondary"
|
||||
@click="edit(data)"
|
||||
/>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
|
||||
<template #empty>
|
||||
<p>{{ store.fetching ? $t("loading") : $t("emptyTable") }}</p>
|
||||
</template>
|
||||
<template #footer>
|
||||
<Pagination :total="store.total" />
|
||||
</template>
|
||||
</DataTable>
|
||||
<Galleria
|
||||
v-model:visible="galleryVes"
|
||||
:value="gallery"
|
||||
:numVisible="5"
|
||||
containerClass="w-1/2"
|
||||
circular
|
||||
fullScreen
|
||||
showItemNavigators
|
||||
:showThumbnailNavigators="false"
|
||||
>
|
||||
<template #item="{ item: { itemImageSrc, alt } }">
|
||||
<img :src="itemImageSrc" :alt="alt" class="w-full block" />
|
||||
</template>
|
||||
</Galleria>
|
||||
<Column headerClass="w-32">
|
||||
<template #body="{ data }">
|
||||
<div class="flex justify-end">
|
||||
<Button
|
||||
icon="pi pi-eye"
|
||||
rounded
|
||||
text
|
||||
severity="secondary"
|
||||
@click="show(data)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
</Panel>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useProductsStore } from '@/stores/products';
|
||||
import { jDate } from '@/utils/jDate';
|
||||
import { numberFormat } from '@/utils/numberFormat';
|
||||
import { defineAsyncComponent, inject, onBeforeMount, ref, watchEffect } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const ProductForm = defineAsyncComponent(() => import('../components/ProductForm.vue'));
|
||||
const ProductDetails = defineAsyncComponent(() => import('../components/ProductDetails.vue'));
|
||||
|
||||
const { toast, dialog, confirm, t } = inject('service')
|
||||
|
||||
var gallery = []
|
||||
const galleryVes = ref(false)
|
||||
const openGallery = (images =>{
|
||||
gallery = []
|
||||
images.map(image =>{
|
||||
gallery.push({itemImageSrc:image, alt:"product"})
|
||||
})
|
||||
galleryVes.value = true
|
||||
|
||||
})
|
||||
const show = (product) => {
|
||||
dialog.open(ProductDetails, {
|
||||
props: { modal: true, closable: true, header: t('productDetails') },
|
||||
data: {
|
||||
product
|
||||
}
|
||||
});
|
||||
}
|
||||
const UserDetails = defineAsyncComponent(() => import('../components/UserDetails.vue'));
|
||||
|
||||
const store = useProductsStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const edit = (data) =>{
|
||||
router.push(`/videos/${data._id}`)
|
||||
}
|
||||
|
||||
|
||||
const remove = (data) => {
|
||||
console.log('done', data._id)
|
||||
confirm.require({
|
||||
message: t('Are you sure you want to proceed?'),
|
||||
header: t('Danger Zone'),
|
||||
icon: 'pi pi-info-circle',
|
||||
acceptClass: 'p-button-danger p-button-sm',
|
||||
rejectClass: 'p-button-secondary p-button-outlined p-button-sm',
|
||||
rejectLabel: t('cancel'),
|
||||
acceptLabel: t('beOmitted'),
|
||||
defaultFocus: 'reject',
|
||||
accept: async () => {
|
||||
const deleted = await store.delete(data._id)
|
||||
console.log(deleted);
|
||||
await store.index()
|
||||
}
|
||||
// product.removing = true
|
||||
|
||||
// const { status, data } = await store.remove(product._id);
|
||||
|
||||
// product.removing = false
|
||||
|
||||
// if (status === 200)
|
||||
// toast.add({
|
||||
// severity: 'success', summary: t('successful'), detail: t('destroySuccessfully'), life: 3000
|
||||
// });
|
||||
// else
|
||||
// toast.add({
|
||||
// severity: 'error', summary: t('error'), detail: data.msg, life: 3000
|
||||
// });
|
||||
|
||||
// },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
watchEffect(async() => {
|
||||
const { page = 1, pageSize = 10 } = route.query
|
||||
await store.index(page, pageSize)
|
||||
console.log(store.items[2].image);
|
||||
console.log(String.fromCharCode(store.items[2].image)).join('');
|
||||
// store.items.forEach((product) => {
|
||||
// product.status = { value: t('active'), severity: 'success' };
|
||||
// })
|
||||
|
||||
})
|
||||
|
||||
const accepter = async (id,confirmToShow)=>{
|
||||
const { status, data } = await store.confirm(id, {confirmToShow});
|
||||
|
||||
|
||||
if (status === 200)
|
||||
toast.add({
|
||||
severity: 'success', summary: t('successful'), detail: t('destroySuccessfully'), life: 3000
|
||||
});
|
||||
else
|
||||
toast.add({
|
||||
severity: 'error', summary: t('error'), detail: data.msg, life: 3000
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user