create ticket section

This commit is contained in:
HAM!DREZA
2024-06-28 00:10:21 +03:30
parent a3c5108151
commit 6b6b6d53aa
9 changed files with 535 additions and 188 deletions
+1
View File
@@ -16,6 +16,7 @@ declare module 'vue' {
DataTable: typeof import('primevue/datatable')['default']
Dropdown: typeof import('primevue/dropdown')['default']
Editor: typeof import('primevue/editor')['default']
Fieldset: typeof import('primevue/fieldset')['default']
FirstStep: typeof import('./src/components/ProductFormSteps/FirstStep.vue')['default']
FloatLabel: typeof import('primevue/floatlabel')['default']
Galleria: typeof import('primevue/galleria')['default']
+1 -1
View File
@@ -79,7 +79,7 @@ const items = ref([
},
{
label: t('سوالات متداول'),
icon: 'pi pi-exclamation-circle',
icon: 'pi pi-question-circle',
command: () => router.push('/pages/Faq')
},
// {
+5
View File
@@ -83,6 +83,11 @@ const router = createRouter({
name: 'EditCoupon',
component: () => import('@/views/EditCoupon.vue'),
},
{
path: '/tickets/:id',
name: 'ShowTickets',
component: () => import('@/views/ShowTickets.vue'),
},
{
path: '/tickets',
name: 'Tickets',
+1 -4
View File
@@ -48,13 +48,10 @@ export const useCategoriesStore = defineStore('categories', {
actions: {
async index(page, rows) {
this.fetching = true
const params = { page, rows }
const { status, data } = await axios.get('/courseCategory', { params })
this.fetching = false
return data
if (status === 200) {
const map = (items, parent = null) => {
return items.map((item) => {
+207
View File
@@ -0,0 +1,207 @@
import axios from '../plugins/axios'
import { defineStore } from 'pinia'
export const useTicketsStore = defineStore('tickets', {
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 index(page, rows) {
this.fetching = true
const params = { page, rows }
const { status, data } = await axios.get('/ticket', { params })
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 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 reply(form){
this.fetching = true
const { status, data } = await axios.post('/ticket/message', form)
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;
},
}
})
+38 -28
View File
@@ -25,10 +25,8 @@
<div class="flex flex-col gap-1">
<FloatLabel>
<Dropdown
:options="levels"
:optionLabel="levels"
:optionValue="levels"
v-model="newCourse.level"
v-model="level"
:options="['مبتدی', 'متوسط', 'پیشرفته']"
class="w-full"
/>
<label>{{ $t("سطح دوره") }}</label>
@@ -41,12 +39,12 @@
</FloatLabel>
</div>
<div class="flex flex-col gap-1">
<!-- <div class="flex flex-col gap-1">
<FloatLabel>
<InputText class="w-full" v-model="newCourse.categories" />
<label>{{ $t("دسته بندی") }}</label>
</FloatLabel>
</div>
</div> -->
<div class="flex flex-col gap-1">
<FloatLabel>
@@ -71,20 +69,7 @@
<label>{{ $t("حجم کل دوره") }}</label>
</FloatLabel>
</div>
</div>
<div class="flex w-full mt-7">
<div class="basis-4/6">
<FloatLabel>
<Editor
v-model="newCourse.description"
placeholder="توضیحات"
editorStyle="height: 320px"
/>
</FloatLabel>
</div>
<div class="basis-2/6 ms-10">
<div class="flex flex-col gap-1">
<FloatLabel>
<InputGroup>
@@ -103,8 +88,21 @@
<label>{{ $t("image") }}</label>
</FloatLabel>
</div>
</div>
<div class="card flex flex-col gap-2 justify-center mt-7">
<div class="flex w-full mt-7">
<div class="basis-4/6">
<FloatLabel>
<Editor
v-model="newCourse.description"
placeholder="توضیحات"
editorStyle="height: 320px"
/>
</FloatLabel>
</div>
<div class="basis-2/6 ms-8">
<div class="card flex flex-col gap-2 justify-center">
<Listbox
listStyle="height:240px"
emptyMessage="دسته بندی وجود ندارد"
@@ -152,6 +150,7 @@
</template>
<script setup>
import Listbox from "primevue/listbox";
import { jDate } from "@/utils/jDate";
import { treeTogglerRTL } from "@/utils/tree-toggler-rtl";
@@ -168,10 +167,21 @@ import {
import { useStaticsStore } from "@/stores/statics";
import { useUploaderStore } from "@/stores/uploader";
import { useProductsStore } from "@/stores/products";
const courseStore = useProductsStore()
import { useCategoriesStore } from "@/stores/categoreis";
const categoriesStore = useCategoriesStore();
const courseStore = useProductsStore();
const uploadStore = useUploaderStore();
import { useRoute } from "vue-router";
const route = useRoute();
const level = ref(null)
watchEffect(async () => {
const { page = 1, rows = 10 } = route.query;
const categoriess = await categoriesStore.index(page, rows);
console.log("cat", categoriess);
cities.value = categoriess;
});
const selectedCity = ref();
const cities = ref();
const levels = reactive(["آسان", "متوسط", "سخت"]);
const newCourse = reactive({
name: "",
@@ -193,7 +203,7 @@ const chooseImage = async (el) => {
//console.log('uploader', uploader.data.url);
newCourse.image = uploader.data.url;
console.log("تصویر آپلود شد");
console.log('upldr',uploader);
console.log("upldr", uploader);
};
const create = async (data) => {
if (newCourse.image === "") {
@@ -219,20 +229,20 @@ const create = async (data) => {
newCourse.slug = data.name;
newCourse.duration = data.duration;
// newCourse.categories = data.categories;
newCourse.categories = "66587ae698d0a0db14693a5e"
newCourse.categories = "66587ae698d0a0db14693a5e";
// newCourse.teacher = data.teacher;
newCourse.teacher = "66530b0480ef484e2bd19220"
newCourse.teacher = "66530b0480ef484e2bd19220";
// newCourse.completionPercentage = data.completionPercentage;
newCourse.completionPercentage = 20;
// newCourse.level = data.level;
newCourse.level = "متوسط"
newCourse.level = "متوسط";
// newCourse.language = data.language;
newCourse.language = "فارسی"
newCourse.language = "فارسی";
newCourse.countOfVideos = data.countOfVideos;
newCourse.totalFileSize = data.totalFileSize;
// console.log("newCourse: ", newCourse);
const creat = await courseStore.create(newCourse)
console.log('created', creat);
const creat = await courseStore.create(newCourse);
console.log("created", creat);
};
// console.log("form", data);
// console.log("json", JSON.stringify(data));
+45 -46
View File
@@ -1,5 +1,4 @@
<template>
<section class="flex flex-col gap-4">
<Panel :header="$t('ویرایش دوره')">
<div class="grid grid-cols-3 gap-7">
@@ -29,7 +28,7 @@
:options="levels"
:optionLabel="levels"
:optionValue="levels"
v-model="course.level"
class="w-full"
/>
<label>{{ $t("سطح دوره") }}</label>
@@ -37,55 +36,33 @@
</div>
<div class="flex flex-col gap-1">
<FloatLabel>
<InputText class="w-full" />
<InputText class="w-full" v-model="course.teacher.name" />
<label>{{ $t("مدرس دوره") }}</label>
</FloatLabel>
</div>
<div class="flex flex-col gap-1">
<FloatLabel>
<InputText class="w-full" />
<label>{{ $t("دسته بندی") }}</label>
</FloatLabel>
</div>
<div class="flex flex-col gap-1">
<FloatLabel>
<InputText
class="w-full"
/>
<InputText class="w-full" v-model="course.completionPercentage" />
<label>{{ $t("درصد تکمیل دوره") }}</label>
</FloatLabel>
</div>
<div class="flex flex-col gap-1">
<FloatLabel>
<InputText class="w-full" />
<InputText class="w-full" v-model="course.videosCount" />
<label>{{ $t("تعداد ویدیو ها") }}</label>
</FloatLabel>
</div>
<div class="flex flex-col gap-1">
<FloatLabel>
<InputText class="w-full" />
<InputText class="w-full" v-model="course.totalFileSize" />
<label>{{ $t("حجم کل دوره") }}</label>
</FloatLabel>
</div>
</div>
<div class="flex w-full mt-7">
<div class="basis-4/6">
<FloatLabel>
<Editor
placeholder="توضیحات"
editorStyle="height: 320px"
/>
</FloatLabel>
</div>
<div class="basis-2/6 ms-10">
<div class="flex flex-col gap-1">
<FloatLabel>
<InputGroup>
@@ -94,6 +71,7 @@
<i class="pi pi-image ml-2"></i>
<span>{{ $t("chooseImage") }}</span>
<input
:value="course.image"
type="file"
class="opacity-0 absolute inset-0 cursor-pointer"
accept="image/*"
@@ -104,8 +82,21 @@
<label>{{ $t("image") }}</label>
</FloatLabel>
</div>
</div>
<div class="card flex flex-col gap-2 justify-center mt-7">
<div class="flex w-full mt-7">
<div class="basis-4/6">
<FloatLabel>
<Editor
v-model="course.description"
placeholder="توضیحات"
editorStyle="height: 320px"
/>
</FloatLabel>
</div>
<div class="basis-2/6 ms-8">
<div class="card flex flex-col gap-2 justify-center ">
<Listbox
listStyle="height:240px"
emptyMessage="دسته بندی وجود ندارد"
@@ -121,7 +112,6 @@
</div>
</div>
<div class="flex pt-4 justify-end">
<Button
:label="$t('edit')"
@@ -135,12 +125,13 @@
</section>
</template>
<script setup>
import { useProductsStore } from '@/stores/products';
import { ref, watchEffect, reactive } from 'vue';
import { useProductsStore } from "@/stores/products";
import { ref, watchEffect, reactive } from "vue";
const store = useProductsStore()
const data = ref(null)
const store = useProductsStore();
const data = ref(null);
const course = reactive({
id: "",
name: "",
description: "",
slug: "",
@@ -155,21 +146,31 @@ const course = reactive({
countOfVideos: 0,
totalFileSize: "",
});
import { useRoute } from 'vue-router'
import { useRoute } from "vue-router";
const route = useRoute()
const route = useRoute();
watchEffect(async () => {
data.value = await store.edit(route.params.id)
console.log('store7',data.value.course);
course.name = data.value.course.name
course.language = data.value.course.language
course.duration = data.value.course.duration
})
data.value = await store.edit(route.params.id);
course.id = data.value.course.id;
course.name = data.value.course.name;
course.description = data.value.course.description;
course.slug = data.value.course.slug;
course.duration = data.value.course.duration;
course.image = data.value.course.image;
course.categories = data.value.course.categories;
course.price = data.value.course.price;
course.teacher = data.value.course.teacher;
course.completionPercentage = data.value.course.completionPercentage;
course.level = data.value.course.level;
course.language = data.value.course.language;
course.countOfVideos = data.value.course.countOfVideos;
course.totalFileSize = data.value.course.totalFileSize;
});
// const levels = reactive(["آسان", "متوسط", "سخت"]);
const chooseImage = async (el) => {
// const uploader = await uploadStore.uploader(el.target.value);
// //console.log('uploader', uploader.data.url);
@@ -178,7 +179,5 @@ const chooseImage = async (el) => {
// console.log('upldr',uploader);
};
const create = async (data) => {
}
const create = async (data) => {};
</script>
+68
View File
@@ -0,0 +1,68 @@
<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>
</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>
</div>
<Editor v-model="value" editorStyle="height: 320px" />
<div class="flex pt-4 justify-end">
<Button
:label="$t('send')"
icon="pi pi-send"
severity="success"
:loading="saving"
@click="send(value)"
/>
</div>
</Panel>
</template>
<script setup>
import { ref, watchEffect } from "vue";
import { useRoute } from "vue-router";
const value = ref("");
import { useTicketsStore } from "@/stores/tickets";
import { jDate } from "@/utils/jDate";
const store = useTicketsStore();
const messages = ref([]);
const route = useRoute();
console.log(route.params.id);
const send = async (value) => {
console.log(value);
const sended = await store.reply({
content: value,
ticketId: route.params.id,
});
console.log('sended', sended);
};
watchEffect(async () => {
messages.value = await store.ticket(route.params.id);
console.log("mess", messages.value.ticketMessages);
});
</script>
+61 -1
View File
@@ -1,3 +1,63 @@
<template>
<h1>tickets</h1>
<Panel :header="$t('تیکت ها')">
<DataTable :value="data">
<Column field="subject" :header="$t('موضوع')" />
<Column field="id" :header="$t('id')" />
<Column :field="({ createdBy }) => createdBy.name" :header="$t('کاربر')" />
<Column field="status" :header="$t('status')" />
<Column :field="({ createdAt }) => jDate(createdAt)" :header="$t('createdAt')" />
<Column :header="$t('options')" headerClass="w-32">
<template #body="{ data }">
<div class="flex">
<Button
icon="pi pi-comments"
rounded
text
severity="secondary"
@click="chat(data)"
/>
<Button icon="pi pi-times-circle" style="color: red" rounded text severity="secondary" @click="closeTicket(data)" />
</div>
</template>
</Column>
<template #empty>
<p>{{ store.fetching ? $t("loading") : $t("emptyTable") }}</p>
</template>
<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 { ref, watchEffect } from "vue";
import {useTicketsStore} from "@/stores/tickets"
import { useRoute, useRouter } from "vue-router";
import {jDate} from "@/utils/jDate"
const store = useTicketsStore()
const router = useRouter()
const data = ref([])
const route = useRoute()
const chat = (data) =>{
router.push(`/tickets/${data.id}`)
console.log(data.id);
}
const closeTicket = async(data) =>{
// console.log(data.id);
const closess = await store.close(data.id)
console.log('close',closess);
await store.index()
}
watchEffect(async() =>{
const { page = 1, pageSize = 10 } = route.query;
data.value = await store.index(page, pageSize)
console.log(data.value[1]);
})
</script>