diff --git a/src/App.vue b/src/App.vue
index 3ae8ca5..fba44b9 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -15,6 +15,7 @@ import { useGetMeQuery } from '@/services/query/auth'
import PublicLayout from '@/layouts/PublicLayout.vue'
import StudentLayout from '@/layouts/StudentLayout.vue'
import ConfirmModal from '@/components/ConfirmModal.vue'
+import CounselorLayout from '@/layouts/CounselorLayout.vue'
import { tokenService } from '@/services/api/token-service'
import { VueQueryDevtools } from '@tanstack/vue-query-devtools'
@@ -24,6 +25,7 @@ const layouts = {
public: PublicLayout,
admin: AdminLayout,
student: StudentLayout,
+ counselor: CounselorLayout,
}
const currentLayout = computed(() => layouts[route.meta?.layout] || PublicLayout)
diff --git a/src/enums/index.js b/src/enums/index.js
index 75f4168..86722a7 100644
--- a/src/enums/index.js
+++ b/src/enums/index.js
@@ -165,6 +165,10 @@ export const ASSIGNMENT_PRIORITY = Object.freeze({
optional: 'اختیاری',
})
+// Single ticket-lifecycle vocabulary. Used by every ticket-like resource —
+// admin /messages, admin /services, admin /consultations, counselor, student
+// /my-tickets, /user/services, /user/consultants. Backend exposes exactly
+// these three values across all of them.
export const TICKET_STATUS = Object.freeze({
open: 'باز',
answered: 'پاسخ داده شده',
@@ -182,22 +186,9 @@ export const PRIORITY_STATUS = Object.freeze({
low: 'پایین',
})
-export const SERVICE_STATUS = Object.freeze({
- approved: 'تایید شده',
- pending: 'در انتظار بررسی',
- in_progress: 'در حال انجام',
- completed: 'تکمیل شده',
-})
-
export const SERVICE_TYPE = Object.freeze({
loan: 'وام',
consultation: 'مشاوره',
educational: 'آموزشی',
other: 'سایر',
})
-
-export const CONSULTATION_STATUS = Object.freeze({
- open: 'در حال گفتگو',
- answered: 'پاسخ داده شده',
- closed: 'بسته شده',
-})
diff --git a/src/features/admin/consultations/components/ConsultationItem.vue b/src/features/admin/consultations/components/ConsultationItem.vue
index 2747f65..01b2ec8 100644
--- a/src/features/admin/consultations/components/ConsultationItem.vue
+++ b/src/features/admin/consultations/components/ConsultationItem.vue
@@ -17,19 +17,15 @@
@@ -55,12 +51,19 @@
diff --git a/src/features/admin/services/pages/ServicesListPage.vue b/src/features/admin/services/pages/ServicesListPage.vue
index e7ac00e..cc6aab2 100644
--- a/src/features/admin/services/pages/ServicesListPage.vue
+++ b/src/features/admin/services/pages/ServicesListPage.vue
@@ -18,7 +18,8 @@
-
+
+
@@ -37,56 +40,73 @@
diff --git a/src/features/counselor/components/modals/CounselorConsultationDetailsModal.vue b/src/features/counselor/components/modals/CounselorConsultationDetailsModal.vue
new file mode 100644
index 0000000..b3cfcfc
--- /dev/null
+++ b/src/features/counselor/components/modals/CounselorConsultationDetailsModal.vue
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+
+ {{ consultationDate }}
+
+
+
+
+
+
+
{{ message.message }}
+
{{ messageTime(message) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/features/counselor/pages/CounselorConsultationsPage.vue b/src/features/counselor/pages/CounselorConsultationsPage.vue
new file mode 100644
index 0000000..a202947
--- /dev/null
+++ b/src/features/counselor/pages/CounselorConsultationsPage.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/features/counselor/router.js b/src/features/counselor/router.js
new file mode 100644
index 0000000..38147b5
--- /dev/null
+++ b/src/features/counselor/router.js
@@ -0,0 +1,8 @@
+export default [
+ {
+ path: '/counselor/consultations',
+ name: 'counselor-consultations',
+ component: () => import('@/features/counselor/pages/CounselorConsultationsPage.vue'),
+ meta: { layout: 'counselor', role: 'counselor', title: 'مشاوره' },
+ },
+]
diff --git a/src/features/student/consultants/components/ConsultantItem.vue b/src/features/student/consultants/components/ConsultantItem.vue
index 03e38b6..502f5e6 100644
--- a/src/features/student/consultants/components/ConsultantItem.vue
+++ b/src/features/student/consultants/components/ConsultantItem.vue
@@ -22,10 +22,14 @@
import { computed } from 'vue'
import Badge from '@/components/Badge.vue'
+// Backend statuses are open/answered/closed. Legacy keys stay in place for any
+// pre-migration cached data.
const TONE_MAP = {
approved: 'success',
rejected: 'danger',
pending: 'neutral',
+ open: 'neutral',
+ answered: 'success',
closed: 'info',
}
diff --git a/src/features/student/consultants/components/RequestConsultantForm.vue b/src/features/student/consultants/components/RequestConsultantForm.vue
index b0b7b11..20ff6a3 100644
--- a/src/features/student/consultants/components/RequestConsultantForm.vue
+++ b/src/features/student/consultants/components/RequestConsultantForm.vue
@@ -3,15 +3,12 @@
diff --git a/src/features/student/tickets/components/TicketItem.vue b/src/features/student/tickets/components/TicketItem.vue
new file mode 100644
index 0000000..fd83ca3
--- /dev/null
+++ b/src/features/student/tickets/components/TicketItem.vue
@@ -0,0 +1,168 @@
+
+
+
+
+
![]()
+
+
+
+
+
+
{{ userName }}
+
+ شماره درخواست :
+ {{ ticket.id || '—' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/features/student/tickets/components/modals/CreateTicketModal.vue b/src/features/student/tickets/components/modals/CreateTicketModal.vue
new file mode 100644
index 0000000..d03a377
--- /dev/null
+++ b/src/features/student/tickets/components/modals/CreateTicketModal.vue
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/features/student/tickets/components/modals/TicketDetailsModal.vue b/src/features/student/tickets/components/modals/TicketDetailsModal.vue
new file mode 100644
index 0000000..ed79c1a
--- /dev/null
+++ b/src/features/student/tickets/components/modals/TicketDetailsModal.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
+
+
+ {{ requestDate }}
+
+
+
+
+
{{ message.message }}
+
{{ messageTime(message) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/features/student/tickets/pages/StudentTicketsPage.vue b/src/features/student/tickets/pages/StudentTicketsPage.vue
new file mode 100644
index 0000000..cf88bbb
--- /dev/null
+++ b/src/features/student/tickets/pages/StudentTicketsPage.vue
@@ -0,0 +1,207 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/layouts/CounselorLayout.vue b/src/layouts/CounselorLayout.vue
new file mode 100644
index 0000000..c8d61e6
--- /dev/null
+++ b/src/layouts/CounselorLayout.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
+
+
diff --git a/src/layouts/StudentLayout.vue b/src/layouts/StudentLayout.vue
index e2a4591..5234c8c 100644
--- a/src/layouts/StudentLayout.vue
+++ b/src/layouts/StudentLayout.vue
@@ -95,6 +95,12 @@ const menuItems = computed(() => [
to: { name: 'student-consultants' },
active: route.name === 'student-consultants',
},
+ {
+ title: 'تیکتهای من',
+ icon: 'chat-centered-dots',
+ to: { name: 'student-tickets' },
+ active: route.name === 'student-tickets',
+ },
{
title: 'گواهینامه ها',
icon: 'scroll',
diff --git a/src/layouts/sidebars/CounselorSidebar.vue b/src/layouts/sidebars/CounselorSidebar.vue
new file mode 100644
index 0000000..74a8bc6
--- /dev/null
+++ b/src/layouts/sidebars/CounselorSidebar.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
diff --git a/src/services/api/admin-services.js b/src/services/api/admin-services.js
new file mode 100644
index 0000000..42deba2
--- /dev/null
+++ b/src/services/api/admin-services.js
@@ -0,0 +1,16 @@
+// Admin services UI hits the same /admin/tickets endpoints as the messages
+// UI, scoped by ?type=service. Show/send/status reuse the admin-tickets
+// helpers since the URL pattern is identical.
+import { http } from '@/services/api/http'
+import { buildUrl, endpoints } from '@/services/api/endpoints'
+
+export const apiGetAdminServices = (params) =>
+ http.get(endpoints.getTicketsList, { params: { ...params, type: 'service' } })
+
+export const apiShowAdminService = (id) => http.get(buildUrl(endpoints.showTicket, { id }))
+
+export const apiSendAdminServiceMessage = (id, payload) =>
+ http.post(buildUrl(endpoints.sendTicketMessage, { id }), payload)
+
+export const apiChangeAdminServiceStatus = (id, payload) =>
+ http.patch(buildUrl(endpoints.changeTicketStatus, { id }), payload)
diff --git a/src/services/api/counselor-tickets.js b/src/services/api/counselor-tickets.js
new file mode 100644
index 0000000..fa74713
--- /dev/null
+++ b/src/services/api/counselor-tickets.js
@@ -0,0 +1,14 @@
+import { http } from '@/services/api/http'
+import { buildUrl, endpoints } from '@/services/api/endpoints'
+
+export const apiGetCounselorTickets = (params) =>
+ http.get(endpoints.getCounselorTickets, { params })
+
+export const apiShowCounselorTicket = (id) =>
+ http.get(buildUrl(endpoints.showCounselorTicket, { id }))
+
+export const apiSendCounselorTicketMessage = (id, payload) =>
+ http.post(buildUrl(endpoints.sendCounselorTicketMessage, { id }), payload)
+
+export const apiChangeCounselorTicketStatus = (id, payload) =>
+ http.patch(buildUrl(endpoints.changeCounselorTicketStatus, { id }), payload)
diff --git a/src/services/api/endpoints.js b/src/services/api/endpoints.js
index 8e56477..3109267 100644
--- a/src/services/api/endpoints.js
+++ b/src/services/api/endpoints.js
@@ -35,14 +35,12 @@ export const endpoints = {
getMissionaryDispatches: '/student/missionary/dispatches',
getMissionaryNarratives: '/student/missionary/narratives',
- getStudentServices: '/student/services',
- createStudentService: '/student/services',
- getStudentServiceTypes: '/student/service-types',
- getStudentServiceTitles: '/student/service-titles',
-
- getStudentConsultants: '/student/consultants',
- createStudentConsultant: '/student/consultants',
- getStudentConsultantTitles: '/student/consultant-titles',
+ // Unified student tickets — handles services (type=service), consultants
+ // (type=advise) and inbox (type=ticket) via the same endpoint with a filter.
+ getStudentTickets: '/student/tickets',
+ createStudentTicket: '/student/tickets',
+ showStudentTicket: '/student/tickets/:id',
+ sendStudentTicketMessage: '/student/tickets/:id/messages',
getStudentCertificates: '/student/certificates',
downloadStudentCertificate: '/student/certificates/:id/download',
@@ -140,18 +138,25 @@ export const endpoints = {
showAssignmentSubmission: '/homework-submissions/:submissionId',
// ─── Backend-aligned: Tickets ─────────────────────────────────────────────
- // Admin "messages" UI talks to /admin/tickets.
- // Admin "consultations" UI talks to /counselor/tickets (same schema; the
- // counselor scope is the consultation pool).
+ // Admin sees every type via /admin/tickets, filtered by ?type=:
+ // - messages page → ?type=ticket
+ // - services page → ?type=service
+ // - consultations page → ?type=advise
+ // Counselor is restricted to /counselor/tickets (advise-only at the backend).
getTicketsList: '/admin/tickets',
showTicket: '/admin/tickets/:id',
sendTicketMessage: '/admin/tickets/:id/messages',
changeTicketStatus: '/admin/tickets/:id/status',
- getConsultationsList: '/counselor/tickets',
- showConsultation: '/counselor/tickets/:id',
- sendConsultationMessage: '/counselor/tickets/:id/messages',
- changeConsultationStatus: '/counselor/tickets/:id/status',
+ getConsultationsList: '/admin/tickets',
+ showConsultation: '/admin/tickets/:id',
+ sendConsultationMessage: '/admin/tickets/:id/messages',
+ changeConsultationStatus: '/admin/tickets/:id/status',
+
+ getCounselorTickets: '/counselor/tickets',
+ showCounselorTicket: '/counselor/tickets/:id',
+ sendCounselorTicketMessage: '/counselor/tickets/:id/messages',
+ changeCounselorTicketStatus: '/counselor/tickets/:id/status',
}
export const buildUrl = (template, params = {}) =>
diff --git a/src/services/api/student-consultants.js b/src/services/api/student-consultants.js
deleted file mode 100644
index 2fdedc8..0000000
--- a/src/services/api/student-consultants.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { http } from '@/services/api/http'
-import { endpoints } from '@/services/api/endpoints'
-
-export const apiGetStudentConsultants = (params) =>
- http.get(endpoints.getStudentConsultants, { params })
-
-export const apiCreateStudentConsultant = (payload) =>
- http.post(endpoints.createStudentConsultant, payload)
-
-export const apiGetStudentConsultantTitles = () => http.get(endpoints.getStudentConsultantTitles)
diff --git a/src/services/api/student-services.js b/src/services/api/student-services.js
deleted file mode 100644
index 128ef82..0000000
--- a/src/services/api/student-services.js
+++ /dev/null
@@ -1,12 +0,0 @@
-import { http } from '@/services/api/http'
-import { endpoints } from '@/services/api/endpoints'
-
-export const apiGetStudentServices = (params) => http.get(endpoints.getStudentServices, { params })
-
-export const apiCreateStudentService = (payload) =>
- http.post(endpoints.createStudentService, payload)
-
-export const apiGetStudentServiceTypes = () => http.get(endpoints.getStudentServiceTypes)
-
-export const apiGetStudentServiceTitles = (params) =>
- http.get(endpoints.getStudentServiceTitles, { params })
diff --git a/src/services/api/student-tickets.js b/src/services/api/student-tickets.js
new file mode 100644
index 0000000..8b1e1cd
--- /dev/null
+++ b/src/services/api/student-tickets.js
@@ -0,0 +1,11 @@
+import { http } from '@/services/api/http'
+import { buildUrl, endpoints } from '@/services/api/endpoints'
+
+export const apiGetStudentTickets = (params) => http.get(endpoints.getStudentTickets, { params })
+
+export const apiShowStudentTicket = (id) => http.get(buildUrl(endpoints.showStudentTicket, { id }))
+
+export const apiCreateStudentTicket = (payload) => http.post(endpoints.createStudentTicket, payload)
+
+export const apiSendStudentTicketMessage = (id, payload) =>
+ http.post(buildUrl(endpoints.sendStudentTicketMessage, { id }), payload)
diff --git a/src/services/mock/fixtures/admin-consultations.js b/src/services/mock/fixtures/admin-consultations.js
deleted file mode 100644
index 24f54bf..0000000
--- a/src/services/mock/fixtures/admin-consultations.js
+++ /dev/null
@@ -1,86 +0,0 @@
-export const adminConsultations = [
- {
- id: 387,
- requestId: '387',
- user: {
- id: 201,
- firstName: 'علیرضا',
- lastName: 'محمدی',
- fullName: 'علیرضا محمدی',
- avatarUrl: '',
- },
- status: 'answered',
- statusLabel: 'پاسخ داده شده',
- createdAt: '2026-05-12T13:54:00.000Z',
- faCreatedAt: '۱۴۰۵/۰۲/۲۲',
- faCreatedTime: '۱۳:۵۴',
- messages: [
- {
- id: 1,
- sender: 'user',
- text: 'سلام، در خصوص انتخاب رشته مشاوره میخواستم.',
- time: '۱۳:۵۴',
- },
- {
- id: 2,
- sender: 'admin',
- text: 'سلام و وقت بخیر، لطفا رشته فعلی و علاقهمندیهای خود را بفرمایید.',
- time: '۱۴:۰۲',
- },
- ],
- },
- {
- id: 388,
- requestId: '388',
- user: {
- id: 202,
- firstName: 'زهرا',
- lastName: 'کریمی',
- fullName: 'زهرا کریمی',
- avatarUrl: '',
- },
- status: 'in_progress',
- statusLabel: 'در حال گفتگو',
- createdAt: '2026-05-11T09:30:00.000Z',
- faCreatedAt: '۱۴۰۵/۰۲/۲۱',
- faCreatedTime: '۰۹:۳۰',
- messages: [
- {
- id: 1,
- sender: 'user',
- text: 'برای ادامه تحصیل در سطح ۳ راهنمایی نیاز دارم.',
- time: '۰۹:۳۰',
- },
- ],
- },
- {
- id: 389,
- requestId: '389',
- user: {
- id: 203,
- firstName: 'محمد',
- lastName: 'حسینی',
- fullName: 'محمد حسینی',
- avatarUrl: '',
- },
- status: 'closed',
- statusLabel: 'بسته شده',
- createdAt: '2026-05-09T16:10:00.000Z',
- faCreatedAt: '۱۴۰۵/۰۲/۱۹',
- faCreatedTime: '۱۶:۱۰',
- messages: [
- {
- id: 1,
- sender: 'user',
- text: 'مشاوره دریافت شد، با تشکر.',
- time: '۱۶:۱۰',
- },
- {
- id: 2,
- sender: 'admin',
- text: 'موفق باشید.',
- time: '۱۶:۱۲',
- },
- ],
- },
-]
diff --git a/src/services/mock/fixtures/admin-tickets.js b/src/services/mock/fixtures/admin-tickets.js
index 8bdb617..d20791a 100644
--- a/src/services/mock/fixtures/admin-tickets.js
+++ b/src/services/mock/fixtures/admin-tickets.js
@@ -1,9 +1,10 @@
// Shape mirrors backend Postman doc for /admin/tickets:
-// ticket: { id, student_id, assigned_to_user_id, target_role, status, subject,
-// created_at, student, assignee, messages: [{ id, ticket_id,
-// sender_id, message, created_at, sender }] }
-// Field names use camelCase here (snake_case bridge happens at the HTTP layer
-// when we swap the mock for the real backend).
+// ticket: { id, student_id, assigned_to_user_id, type, category, status,
+// subject, created_at, media: [],
+// student, assignee,
+// messages: [{ id, ticket_id, sender_id, message, created_at, sender }] }
+// Fixture uses camelCase — the mock adapter returns it as-is, mimicking what
+// the FE sees post-camelize when wired to the real backend.
const makeUser = (overrides) => ({
id: overrides.id,
@@ -46,6 +47,26 @@ const studentAli = makeUser({
createdAt: '2026-02-10T10:00:00.000Z',
})
+const studentReza = makeUser({
+ id: 104,
+ name: 'رضا حسینی',
+ email: 'reza@example.com',
+ phone: '+989124444444',
+ roles: ['student'],
+ avatarUrl: '',
+ createdAt: '2026-02-12T10:00:00.000Z',
+})
+
+const studentZahra = makeUser({
+ id: 105,
+ name: 'زهرا کریمی',
+ email: 'zahra@example.com',
+ phone: '+989125555555',
+ roles: ['student'],
+ avatarUrl: '',
+ createdAt: '2026-02-15T10:00:00.000Z',
+})
+
const adminUser = makeUser({
id: 1,
name: 'مدیر سامانه',
@@ -54,15 +75,25 @@ const adminUser = makeUser({
createdAt: '2026-01-01T00:00:00.000Z',
})
+const counselorUser = makeUser({
+ id: 7,
+ name: 'مشاور سامانه',
+ email: 'counselor@example.com',
+ roles: ['counselor'],
+ createdAt: '2026-01-15T00:00:00.000Z',
+})
+
export const adminTickets = [
{
id: 401,
studentId: studentJane.id,
assignedToUserId: adminUser.id,
- targetRole: 'admin',
+ type: 'ticket',
+ category: 'enrollment',
status: 'answered',
subject: 'پیگیری وضعیت ثبتنام',
createdAt: '2026-05-09T10:30:00.000Z',
+ media: [],
student: studentJane,
assignee: adminUser,
messages: [
@@ -96,10 +127,12 @@ export const adminTickets = [
id: 402,
studentId: studentMaryam.id,
assignedToUserId: null,
- targetRole: 'admin',
+ type: 'ticket',
+ category: 'schedule',
status: 'open',
subject: 'سوال درباره جلسه آموزشی',
createdAt: '2026-05-08T09:15:00.000Z',
+ media: [],
student: studentMaryam,
assignee: null,
messages: [
@@ -117,10 +150,12 @@ export const adminTickets = [
id: 403,
studentId: studentAli.id,
assignedToUserId: adminUser.id,
- targetRole: 'admin',
+ type: 'ticket',
+ category: 'billing',
status: 'closed',
subject: 'درخواست بستن تیکت',
createdAt: '2026-05-07T14:10:00.000Z',
+ media: [],
student: studentAli,
assignee: adminUser,
messages: [
@@ -142,7 +177,138 @@ export const adminTickets = [
},
],
},
+ {
+ id: 501,
+ studentId: studentReza.id,
+ assignedToUserId: adminUser.id,
+ type: 'service',
+ category: 'loan',
+ status: 'open',
+ subject: 'درخواست وام تحصیلی',
+ createdAt: '2026-05-12T13:54:00.000Z',
+ media: [],
+ student: studentReza,
+ assignee: adminUser,
+ messages: [
+ {
+ id: 1,
+ ticketId: 501,
+ senderId: studentReza.id,
+ message: 'برای ادامه تحصیل به وام نیاز دارم.',
+ createdAt: '2026-05-12T13:54:00.000Z',
+ sender: studentReza,
+ },
+ ],
+ },
+ {
+ id: 502,
+ studentId: studentJane.id,
+ assignedToUserId: null,
+ type: 'service',
+ category: 'insurance',
+ status: 'answered',
+ subject: 'درخواست بیمه دانشجویی',
+ createdAt: '2026-05-10T08:20:00.000Z',
+ media: [],
+ student: studentJane,
+ assignee: null,
+ messages: [
+ {
+ id: 1,
+ ticketId: 502,
+ senderId: studentJane.id,
+ message: 'لطفا اطلاعات بیمه را ارسال کنید.',
+ createdAt: '2026-05-10T08:20:00.000Z',
+ sender: studentJane,
+ },
+ ],
+ },
+ {
+ id: 601,
+ studentId: studentZahra.id,
+ assignedToUserId: counselorUser.id,
+ type: 'advise',
+ category: 'education',
+ status: 'answered',
+ subject: 'مشاوره انتخاب رشته',
+ createdAt: '2026-05-12T13:54:00.000Z',
+ media: [],
+ student: studentZahra,
+ assignee: counselorUser,
+ messages: [
+ {
+ id: 1,
+ ticketId: 601,
+ senderId: studentZahra.id,
+ message: 'سلام، در خصوص انتخاب رشته مشاوره میخواستم.',
+ createdAt: '2026-05-12T13:54:00.000Z',
+ sender: studentZahra,
+ },
+ {
+ id: 2,
+ ticketId: 601,
+ senderId: counselorUser.id,
+ message: 'سلام و وقت بخیر، لطفا رشته فعلی و علاقهمندیهای خود را بفرمایید.',
+ createdAt: '2026-05-12T14:02:00.000Z',
+ sender: counselorUser,
+ },
+ ],
+ },
+ {
+ id: 602,
+ studentId: studentMaryam.id,
+ assignedToUserId: null,
+ type: 'advise',
+ category: 'career',
+ status: 'open',
+ subject: 'مشاوره مسیر شغلی',
+ createdAt: '2026-05-11T09:30:00.000Z',
+ media: [],
+ student: studentMaryam,
+ assignee: null,
+ messages: [
+ {
+ id: 1,
+ ticketId: 602,
+ senderId: studentMaryam.id,
+ message: 'برای ادامه تحصیل در سطح ۳ راهنمایی نیاز دارم.',
+ createdAt: '2026-05-11T09:30:00.000Z',
+ sender: studentMaryam,
+ },
+ ],
+ },
+ {
+ id: 603,
+ studentId: studentAli.id,
+ assignedToUserId: counselorUser.id,
+ type: 'advise',
+ category: 'personal',
+ status: 'closed',
+ subject: 'مشاوره فردی',
+ createdAt: '2026-05-09T16:10:00.000Z',
+ media: [],
+ student: studentAli,
+ assignee: counselorUser,
+ messages: [
+ {
+ id: 1,
+ ticketId: 603,
+ senderId: studentAli.id,
+ message: 'مشاوره دریافت شد، با تشکر.',
+ createdAt: '2026-05-09T16:10:00.000Z',
+ sender: studentAli,
+ },
+ {
+ id: 2,
+ ticketId: 603,
+ senderId: counselorUser.id,
+ message: 'موفق باشید.',
+ createdAt: '2026-05-09T16:12:00.000Z',
+ sender: counselorUser,
+ },
+ ],
+ },
]
-// The signed-in admin used as `sender` when a message is posted from the FE.
export const currentAdminUser = adminUser
+export const currentCounselorUser = counselorUser
diff --git a/src/services/mock/fixtures/student-consultants.js b/src/services/mock/fixtures/student-consultants.js
deleted file mode 100644
index 56591dc..0000000
--- a/src/services/mock/fixtures/student-consultants.js
+++ /dev/null
@@ -1,50 +0,0 @@
-export const studentConsultantTitles = [
- { value: 'course-talk', label: 'گفتگو درباره دوره' },
- { value: 'academic', label: 'مشاوره تحصیلی' },
- { value: 'career', label: 'مشاوره شغلی' },
- { value: 'psychology', label: 'مشاوره روانشناسی' },
- { value: 'other', label: 'سایر' },
-]
-
-const STATUS_LABELS = {
- pending: 'در حال بررسی',
- approved: 'پاسخ داده شده',
- rejected: 'رد شده',
- closed: 'بسته شده',
-}
-
-export const studentConsultants = [
- {
- id: 'c-1',
- requestNumber: 215,
- title: 'درخواست مشاوره',
- topicLabel: 'گفتگو درباره دوره',
- status: 'pending',
- statusLabel: STATUS_LABELS.pending,
- description: 'سوال در مورد محتوای دوره',
- timeLabel: '12:53',
- dateLabel: '1404/12/21',
- },
- {
- id: 'c-2',
- requestNumber: 198,
- title: 'درخواست مشاوره',
- topicLabel: 'مشاوره شغلی',
- status: 'approved',
- statusLabel: STATUS_LABELS.approved,
- description: 'راهنمایی برای انتخاب مسیر شغلی',
- timeLabel: '09:12',
- dateLabel: '1404/11/05',
- },
- {
- id: 'c-3',
- requestNumber: 174,
- title: 'درخواست مشاوره',
- topicLabel: 'مشاوره تحصیلی',
- status: 'rejected',
- statusLabel: STATUS_LABELS.rejected,
- description: 'برنامهریزی درسی',
- timeLabel: '16:40',
- dateLabel: '1404/10/02',
- },
-]
diff --git a/src/services/mock/fixtures/student-services.js b/src/services/mock/fixtures/student-services.js
deleted file mode 100644
index 0dedc07..0000000
--- a/src/services/mock/fixtures/student-services.js
+++ /dev/null
@@ -1,61 +0,0 @@
-export const studentServiceTypes = [
- { value: 'loan', label: 'وام' },
- { value: 'consultation', label: 'مشاوره' },
- { value: 'insurance', label: 'بیمه' },
- { value: 'other', label: 'سایر' },
-]
-
-export const studentServiceTitlesByType = {
- loan: [
- { value: 'tuition-loan', label: 'وام شهریه' },
- { value: 'living-loan', label: 'وام معیشت' },
- ],
- consultation: [
- { value: 'course-talk', label: 'گفتگو درباره دوره' },
- { value: 'career-talk', label: 'مشاوره شغلی' },
- ],
- insurance: [{ value: 'student-insurance', label: 'بیمه دانشجویی' }],
- other: [{ value: 'other-issue', label: 'موضوع دیگر' }],
-}
-
-const STATUS_LABELS = {
- approved: 'تاییــد شـــــده',
- pending: 'در انتظار بررسی',
- rejected: 'رد شده',
-}
-
-export const studentServices = [
- {
- id: 's-1',
- requestNumber: 387,
- title: 'درخواست وام',
- typeKey: 'loan',
- typeLabel: 'وام',
- status: 'approved',
- statusLabel: STATUS_LABELS.approved,
- description: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ',
- createdAt: '1404/05/12',
- },
- {
- id: 's-2',
- requestNumber: 44,
- title: 'درخواست وام',
- typeKey: 'loan',
- typeLabel: 'وام',
- status: 'pending',
- statusLabel: STATUS_LABELS.pending,
- description: 'درخواست شهریه ترم جدید',
- createdAt: '1404/06/02',
- },
- {
- id: 's-3',
- requestNumber: 18,
- title: 'گفتگو درباره دوره',
- typeKey: 'consultation',
- typeLabel: 'مشاوره',
- status: 'rejected',
- statusLabel: STATUS_LABELS.rejected,
- description: 'سوال در مورد محتوای دوره',
- createdAt: '1404/04/22',
- },
-]
diff --git a/src/services/mock/routes/admin-consultations.js b/src/services/mock/routes/admin-consultations.js
deleted file mode 100644
index 99db986..0000000
--- a/src/services/mock/routes/admin-consultations.js
+++ /dev/null
@@ -1,56 +0,0 @@
-import { register } from '@/services/mock/registry'
-import { endpoints } from '@/services/api/endpoints'
-import { adminConsultations } from '@/services/mock/fixtures/admin-consultations'
-import {
- filterDateRange,
- filterItems,
- findOrThrow,
- isoNow,
- makeId,
- paginate,
- updateById,
-} from '@/services/mock/helpers'
-
-const STATUS_LABELS = {
- in_progress: 'در حال گفتگو',
- answered: 'پاسخ داده شده',
- closed: 'بسته شده',
-}
-
-register('GET', endpoints.getConsultationsList, ({ query }) => {
- let list = filterItems(adminConsultations, query, {
- status: 'eq',
- userName: (item, v) =>
- `${item.user?.firstName || ''} ${item.user?.lastName || ''}`
- .toLowerCase()
- .includes(String(v).toLowerCase()),
- })
- list = filterDateRange(list, query)
- return paginate(list, query)
-})
-
-register('GET', endpoints.showConsultation, ({ params }) => ({
- data: findOrThrow(adminConsultations, params.id),
-}))
-
-register('POST', endpoints.sendConsultationMessage, ({ params, data }) => {
- const consultation = findOrThrow(adminConsultations, params.id)
- const message = {
- id: makeId(),
- sender: 'admin',
- text: data.text || '',
- time: 'همین الان',
- sentAt: isoNow(),
- }
- consultation.messages = [...(consultation.messages || []), message]
- consultation.status = 'answered'
- consultation.statusLabel = STATUS_LABELS.answered
- return { data: consultation }
-})
-
-register('POST', endpoints.changeConsultationStatus, ({ params, data }) => ({
- data: updateById(adminConsultations, params.id, {
- status: data.status,
- statusLabel: STATUS_LABELS[data.status] || data.status,
- }),
-}))
diff --git a/src/services/mock/routes/admin-tickets.js b/src/services/mock/routes/admin-tickets.js
index b9f5b73..0069697 100644
--- a/src/services/mock/routes/admin-tickets.js
+++ b/src/services/mock/routes/admin-tickets.js
@@ -13,6 +13,7 @@ import {
register('GET', endpoints.getTicketsList, ({ query }) => {
let list = filterItems(adminTickets, query, {
+ type: 'eq',
status: 'eq',
subject: (item, v) =>
String(item.subject || '')
@@ -51,7 +52,7 @@ register('POST', endpoints.sendTicketMessage, ({ params, data }) => {
}
ticket.messages = [...(ticket.messages || []), message]
ticket.status = 'answered'
- ticket.assigneeId = currentAdminUser.id
+ ticket.assignedToUserId = currentAdminUser.id
ticket.assignee = currentAdminUser
return {
success: true,
diff --git a/src/services/mock/routes/counselor-tickets.js b/src/services/mock/routes/counselor-tickets.js
new file mode 100644
index 0000000..7d7762d
--- /dev/null
+++ b/src/services/mock/routes/counselor-tickets.js
@@ -0,0 +1,58 @@
+import { register } from '@/services/mock/registry'
+import { endpoints } from '@/services/api/endpoints'
+import { adminTickets, currentCounselorUser } from '@/services/mock/fixtures/admin-tickets'
+import {
+ filterDateRange,
+ filterItems,
+ findOrThrow,
+ isoNow,
+ makeId,
+ paginate,
+ updateById,
+} from '@/services/mock/helpers'
+
+// Counselor scope: backend hides every ticket whose type !== 'advise'.
+const counselorPool = () => adminTickets.filter((t) => t.type === 'advise')
+
+register('GET', endpoints.getCounselorTickets, ({ query }) => {
+ let list = filterItems(counselorPool(), query, {
+ type: 'eq',
+ status: 'eq',
+ userName: (item, v) =>
+ String(item.student?.name || '')
+ .toLowerCase()
+ .includes(String(v).toLowerCase()),
+ })
+ list = filterDateRange(list, query)
+ const { data: items, meta } = paginate(list, query)
+ return { success: true, message: 'OK', data: items, meta }
+})
+
+register('GET', endpoints.showCounselorTicket, ({ params }) => ({
+ success: true,
+ message: 'OK',
+ data: findOrThrow(counselorPool(), params.id),
+}))
+
+register('POST', endpoints.sendCounselorTicketMessage, ({ params, data }) => {
+ const ticket = findOrThrow(counselorPool(), params.id)
+ const message = {
+ id: makeId(),
+ ticketId: ticket.id,
+ senderId: currentCounselorUser.id,
+ message: data.message || '',
+ createdAt: isoNow(),
+ sender: currentCounselorUser,
+ }
+ ticket.messages = [...(ticket.messages || []), message]
+ ticket.status = 'answered'
+ ticket.assignedToUserId = currentCounselorUser.id
+ ticket.assignee = currentCounselorUser
+ return { success: true, message: 'Message posted.', data: message }
+})
+
+register('PATCH', endpoints.changeCounselorTicketStatus, ({ params, data }) => ({
+ success: true,
+ message: 'Ticket status updated.',
+ data: updateById(adminTickets, params.id, { status: data.status }),
+}))
diff --git a/src/services/mock/routes/student-consultants.js b/src/services/mock/routes/student-consultants.js
deleted file mode 100644
index f3f4739..0000000
--- a/src/services/mock/routes/student-consultants.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import { paginate } from '@/services/mock/helpers'
-import { register } from '@/services/mock/registry'
-import { endpoints } from '@/services/api/endpoints'
-import {
- studentConsultantTitles,
- studentConsultants,
-} from '@/services/mock/fixtures/student-consultants'
-
-register('GET', endpoints.getStudentConsultants, ({ query }) => paginate(studentConsultants, query))
-
-register('POST', endpoints.createStudentConsultant, ({ data }) => {
- const id = `c-${studentConsultants.length + 1}`
- const next = {
- id,
- requestNumber: 500 + studentConsultants.length + 1,
- title: 'درخواست مشاوره',
- topicLabel:
- studentConsultantTitles.find((t) => t.value === data?.title)?.label || data?.title || '—',
- status: 'pending',
- statusLabel: 'در حال بررسی',
- description: data?.description || '',
- timeLabel: '12:00',
- dateLabel: '1404/06/12',
- }
- studentConsultants.unshift(next)
- return { data: next }
-})
-
-register('GET', endpoints.getStudentConsultantTitles, () => ({ data: studentConsultantTitles }))
diff --git a/src/services/mock/routes/student-services.js b/src/services/mock/routes/student-services.js
deleted file mode 100644
index f99b9bd..0000000
--- a/src/services/mock/routes/student-services.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import { paginate } from '@/services/mock/helpers'
-import { register } from '@/services/mock/registry'
-import { endpoints } from '@/services/api/endpoints'
-import {
- studentServiceTitlesByType,
- studentServiceTypes,
- studentServices,
-} from '@/services/mock/fixtures/student-services'
-
-register('GET', endpoints.getStudentServices, ({ query }) => paginate(studentServices, query))
-
-register('POST', endpoints.createStudentService, ({ data }) => {
- const id = `s-${studentServices.length + 1}`
- const next = {
- id,
- requestNumber: 500 + studentServices.length + 1,
- title: data?.title || '—',
- typeKey: data?.typeKey || 'other',
- typeLabel: studentServiceTypes.find((t) => t.value === data?.typeKey)?.label || 'سایر',
- status: 'pending',
- statusLabel: 'در انتظار بررسی',
- description: data?.description || '',
- createdAt: '1404/06/12',
- }
- studentServices.unshift(next)
- return { data: next }
-})
-
-register('GET', endpoints.getStudentServiceTypes, () => ({ data: studentServiceTypes }))
-
-register('GET', endpoints.getStudentServiceTitles, ({ query }) => ({
- data: studentServiceTitlesByType[query.type] || [],
-}))
diff --git a/src/services/mock/routes/student-tickets.js b/src/services/mock/routes/student-tickets.js
new file mode 100644
index 0000000..71168dc
--- /dev/null
+++ b/src/services/mock/routes/student-tickets.js
@@ -0,0 +1,84 @@
+import { register } from '@/services/mock/registry'
+import { endpoints } from '@/services/api/endpoints'
+import { adminTickets } from '@/services/mock/fixtures/admin-tickets'
+import { filterItems, findOrThrow, isoNow, makeId, paginate } from '@/services/mock/helpers'
+
+// Student scope: backend scopes results to the signed-in student. The mock
+// pretends every ticket whose student is studentJane (id=100) belongs to "me".
+const SELF_STUDENT_ID = 100
+
+const myPool = () => adminTickets.filter((t) => t.studentId === SELF_STUDENT_ID)
+
+register('GET', endpoints.getStudentTickets, ({ query }) => {
+ const list = filterItems(myPool(), query, {
+ type: 'eq',
+ status: 'eq',
+ })
+ const { data: items, meta } = paginate(list, query)
+ return { success: true, message: 'OK', data: items, meta }
+})
+
+register('GET', endpoints.showStudentTicket, ({ params }) => ({
+ success: true,
+ message: 'OK',
+ data: findOrThrow(myPool(), params.id),
+}))
+
+register('POST', endpoints.createStudentTicket, ({ data }) => {
+ const me = myPool()[0]?.student || adminTickets[0].student
+ const ticket = {
+ id: makeId(),
+ studentId: me.id,
+ assignedToUserId: null,
+ type: data.type || 'ticket',
+ category: data.category || null,
+ status: 'open',
+ subject: data.subject || '',
+ createdAt: isoNow(),
+ media: [],
+ student: me,
+ assignee: null,
+ messages: [
+ {
+ id: makeId(),
+ ticketId: 0,
+ senderId: me.id,
+ message: data.message || '',
+ createdAt: isoNow(),
+ sender: me,
+ },
+ ],
+ }
+ ticket.messages[0].ticketId = ticket.id
+ adminTickets.unshift(ticket)
+ return {
+ success: true,
+ message: 'Ticket created.',
+ data: {
+ id: ticket.id,
+ studentId: ticket.studentId,
+ assignedToUserId: ticket.assignedToUserId,
+ type: ticket.type,
+ category: ticket.category,
+ status: ticket.status,
+ subject: ticket.subject,
+ createdAt: ticket.createdAt,
+ media: ticket.media,
+ },
+ }
+})
+
+register('POST', endpoints.sendStudentTicketMessage, ({ params, data }) => {
+ const ticket = findOrThrow(myPool(), params.id)
+ const me = ticket.student
+ const message = {
+ id: makeId(),
+ ticketId: ticket.id,
+ senderId: me.id,
+ message: data.message || '',
+ createdAt: isoNow(),
+ sender: me,
+ }
+ ticket.messages = [...(ticket.messages || []), message]
+ return { success: true, message: 'Message posted.', data: message }
+})
diff --git a/src/services/query/admin-services.js b/src/services/query/admin-services.js
new file mode 100644
index 0000000..652ae22
--- /dev/null
+++ b/src/services/query/admin-services.js
@@ -0,0 +1,44 @@
+import { cleanFilters } from '@/utils/clean-filters'
+import { useMutation, useQuery } from '@tanstack/vue-query'
+import {
+ apiChangeAdminServiceStatus,
+ apiGetAdminServices,
+ apiSendAdminServiceMessage,
+ apiShowAdminService,
+} from '@/services/api/admin-services'
+
+export const adminServicesKeys = {
+ all: ['admin', 'services'],
+ list: (filters, pagination) => ['admin', 'services', 'list', filters, pagination],
+ detail: (id) => ['admin', 'services', 'detail', id],
+}
+
+export const useAdminServicesListQuery = (filtersRef, paginationRef, options = {}) =>
+ useQuery({
+ queryKey: ['admin', 'services', 'list', filtersRef, paginationRef],
+ queryFn: () =>
+ apiGetAdminServices({ ...cleanFilters(filtersRef.value), ...paginationRef.value }),
+ select: (response) => ({
+ data: response?.data ?? [],
+ meta: response?.meta,
+ }),
+ ...options,
+ })
+
+export const useAdminServiceQuery = (idRef, options = {}) =>
+ useQuery({
+ queryKey: ['admin', 'services', 'detail', idRef],
+ queryFn: () => apiShowAdminService(idRef.value),
+ select: (response) => response?.data ?? response,
+ ...options,
+ })
+
+export const useSendAdminServiceMessageMutation = () =>
+ useMutation({
+ mutationFn: ({ id, payload }) => apiSendAdminServiceMessage(id, payload),
+ })
+
+export const useChangeAdminServiceStatusMutation = () =>
+ useMutation({
+ mutationFn: ({ id, payload }) => apiChangeAdminServiceStatus(id, payload),
+ })
diff --git a/src/services/query/admin-tickets.js b/src/services/query/admin-tickets.js
index 48693c3..601bdd4 100644
--- a/src/services/query/admin-tickets.js
+++ b/src/services/query/admin-tickets.js
@@ -13,15 +13,21 @@ export const adminTicketsKeys = {
detail: (id) => ['admin', 'tickets', 'detail', id],
}
+// Backend may wrap the list as `{success, data: [...]}` (flat) or
+// `{data: {data: [...], meta}}` (Laravel default). Normalize.
+const selectList = (response) => {
+ const inner = response?.data
+ if (Array.isArray(inner)) return { data: inner, meta: response?.meta }
+ if (Array.isArray(inner?.data)) return { data: inner.data, meta: inner?.meta ?? response?.meta }
+ return { data: [], meta: response?.meta }
+}
+
export const useAdminTicketsListQuery = (filtersRef, paginationRef, options = {}) =>
useQuery({
queryKey: ['admin', 'tickets', 'list', filtersRef, paginationRef],
queryFn: () =>
apiGetAdminTickets({ ...cleanFilters(filtersRef.value), ...paginationRef.value }),
- select: (response) => ({
- data: response?.data ?? [],
- meta: response?.meta,
- }),
+ select: selectList,
...options,
})
diff --git a/src/services/query/counselor-tickets.js b/src/services/query/counselor-tickets.js
new file mode 100644
index 0000000..825865a
--- /dev/null
+++ b/src/services/query/counselor-tickets.js
@@ -0,0 +1,44 @@
+import { cleanFilters } from '@/utils/clean-filters'
+import { useMutation, useQuery } from '@tanstack/vue-query'
+import {
+ apiChangeCounselorTicketStatus,
+ apiGetCounselorTickets,
+ apiSendCounselorTicketMessage,
+ apiShowCounselorTicket,
+} from '@/services/api/counselor-tickets'
+
+export const counselorTicketsKeys = {
+ all: ['counselor', 'tickets'],
+ list: (filters, pagination) => ['counselor', 'tickets', 'list', filters, pagination],
+ detail: (id) => ['counselor', 'tickets', 'detail', id],
+}
+
+export const useCounselorTicketsListQuery = (filtersRef, paginationRef, options = {}) =>
+ useQuery({
+ queryKey: ['counselor', 'tickets', 'list', filtersRef, paginationRef],
+ queryFn: () =>
+ apiGetCounselorTickets({ ...cleanFilters(filtersRef.value), ...paginationRef.value }),
+ select: (response) => ({
+ data: response?.data ?? [],
+ meta: response?.meta,
+ }),
+ ...options,
+ })
+
+export const useCounselorTicketQuery = (idRef, options = {}) =>
+ useQuery({
+ queryKey: ['counselor', 'tickets', 'detail', idRef],
+ queryFn: () => apiShowCounselorTicket(idRef.value),
+ select: (response) => response?.data ?? response,
+ ...options,
+ })
+
+export const useSendCounselorTicketMessageMutation = () =>
+ useMutation({
+ mutationFn: ({ id, payload }) => apiSendCounselorTicketMessage(id, payload),
+ })
+
+export const useChangeCounselorTicketStatusMutation = () =>
+ useMutation({
+ mutationFn: ({ id, payload }) => apiChangeCounselorTicketStatus(id, payload),
+ })
diff --git a/src/services/query/student-consultants.js b/src/services/query/student-consultants.js
deleted file mode 100644
index 966a80f..0000000
--- a/src/services/query/student-consultants.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { cleanFilters } from '@/utils/clean-filters'
-import { useMutation, useQuery } from '@tanstack/vue-query'
-import {
- apiCreateStudentConsultant,
- apiGetStudentConsultantTitles,
- apiGetStudentConsultants,
-} from '@/services/api/student-consultants'
-
-export const studentConsultantsKeys = {
- all: ['student', 'consultants'],
- list: (filters, pagination) => ['student', 'consultants', 'list', filters, pagination],
- titles: ['student', 'consultant-titles'],
-}
-
-export const useStudentConsultantsQuery = (filtersRef, paginationRef, options = {}) =>
- useQuery({
- queryKey: ['student', 'consultants', 'list', filtersRef, paginationRef],
- queryFn: () =>
- apiGetStudentConsultants({
- ...cleanFilters(filtersRef.value),
- ...paginationRef.value,
- }),
- ...options,
- })
-
-export const useStudentConsultantTitlesQuery = (options = {}) =>
- useQuery({
- queryKey: studentConsultantsKeys.titles,
- queryFn: () => apiGetStudentConsultantTitles(),
- select: (response) => response?.data ?? [],
- ...options,
- })
-
-export const useCreateStudentConsultantMutation = () =>
- useMutation({ mutationFn: (payload) => apiCreateStudentConsultant(payload) })
diff --git a/src/services/query/student-services.js b/src/services/query/student-services.js
deleted file mode 100644
index d30c577..0000000
--- a/src/services/query/student-services.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { cleanFilters } from '@/utils/clean-filters'
-import { useMutation, useQuery } from '@tanstack/vue-query'
-import {
- apiCreateStudentService,
- apiGetStudentServiceTitles,
- apiGetStudentServiceTypes,
- apiGetStudentServices,
-} from '@/services/api/student-services'
-
-export const studentServicesKeys = {
- all: ['student', 'services'],
- list: (filters, pagination) => ['student', 'services', 'list', filters, pagination],
- types: ['student', 'service-types'],
- titles: (typeKey) => ['student', 'service-titles', typeKey],
-}
-
-export const useStudentServicesQuery = (filtersRef, paginationRef, options = {}) =>
- useQuery({
- queryKey: ['student', 'services', 'list', filtersRef, paginationRef],
- queryFn: () =>
- apiGetStudentServices({
- ...cleanFilters(filtersRef.value),
- ...paginationRef.value,
- }),
- ...options,
- })
-
-export const useStudentServiceTypesQuery = (options = {}) =>
- useQuery({
- queryKey: studentServicesKeys.types,
- queryFn: () => apiGetStudentServiceTypes(),
- select: (response) => response?.data ?? [],
- ...options,
- })
-
-export const useStudentServiceTitlesQuery = (typeKeyRef, options = {}) =>
- useQuery({
- queryKey: ['student', 'service-titles', typeKeyRef],
- queryFn: () => apiGetStudentServiceTitles({ type: typeKeyRef.value }),
- select: (response) => response?.data ?? [],
- ...options,
- })
-
-export const useCreateStudentServiceMutation = () =>
- useMutation({ mutationFn: (payload) => apiCreateStudentService(payload) })
diff --git a/src/services/query/student-tickets.js b/src/services/query/student-tickets.js
new file mode 100644
index 0000000..a2a65ed
--- /dev/null
+++ b/src/services/query/student-tickets.js
@@ -0,0 +1,43 @@
+import { cleanFilters } from '@/utils/clean-filters'
+import { useMutation, useQuery } from '@tanstack/vue-query'
+import {
+ apiCreateStudentTicket,
+ apiGetStudentTickets,
+ apiSendStudentTicketMessage,
+ apiShowStudentTicket,
+} from '@/services/api/student-tickets'
+
+export const studentTicketsKeys = {
+ all: ['student', 'tickets'],
+ byType: (type) => ['student', 'tickets', 'type', type],
+ list: (filters, pagination) => ['student', 'tickets', 'list', filters, pagination],
+ detail: (id) => ['student', 'tickets', 'detail', id],
+}
+
+export const useStudentTicketsQuery = (filtersRef, paginationRef, options = {}) =>
+ useQuery({
+ queryKey: ['student', 'tickets', 'list', filtersRef, paginationRef],
+ queryFn: () =>
+ apiGetStudentTickets({ ...cleanFilters(filtersRef.value), ...paginationRef.value }),
+ select: (response) => ({
+ data: response?.data ?? [],
+ meta: response?.meta,
+ }),
+ ...options,
+ })
+
+export const useStudentTicketQuery = (idRef, options = {}) =>
+ useQuery({
+ queryKey: ['student', 'tickets', 'detail', idRef],
+ queryFn: () => apiShowStudentTicket(idRef.value),
+ select: (response) => response?.data ?? response,
+ ...options,
+ })
+
+export const useCreateStudentTicketMutation = () =>
+ useMutation({ mutationFn: (payload) => apiCreateStudentTicket(payload) })
+
+export const useSendStudentTicketMessageMutation = () =>
+ useMutation({
+ mutationFn: ({ id, payload }) => apiSendStudentTicketMessage(id, payload),
+ })