diff --git a/backend/src/admin/admin.controller.ts b/backend/src/admin/admin.controller.ts index 62445c6..ffbf70f 100644 --- a/backend/src/admin/admin.controller.ts +++ b/backend/src/admin/admin.controller.ts @@ -254,6 +254,21 @@ export class AdminController { return { success: true, data: settings }; } + @UseGuards(JwtAuthGuard) + @Get('pets') + @ApiOperation({ summary: 'لیست پت‌ها (مدیریت)' }) + @ApiQuery({ name: 'page', required: false }) + @ApiQuery({ name: 'limit', required: false }) + @ApiQuery({ name: 'search', required: false }) + async getPets( + @Query('page') page?: string, + @Query('limit') limit?: string, + @Query('search') search?: string, + ) { + const data = await this.adminService.getPets({ page, limit, search }); + return { success: true, ...data }; + } + // --- Doctors / Vets --- @Get('doctors') @ApiOperation({ summary: 'لیست پزشکان و متخصصان' }) diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts index afad26a..333e517 100644 --- a/backend/src/admin/admin.service.ts +++ b/backend/src/admin/admin.service.ts @@ -475,6 +475,48 @@ export class AdminService { return this.getSettings(); } + async getPets(query: any) { + const page = Number(query.page) || 1; + const limit = Number(query.limit) || 10; + const skip = (page - 1) * limit; + + const where: any = {}; + if (query.search) { + where.OR = [ + { name: { contains: query.search, mode: 'insensitive' } }, + { breed: { contains: query.search, mode: 'insensitive' } }, + { user: { firstName: { contains: query.search, mode: 'insensitive' } } }, + { user: { lastName: { contains: query.search, mode: 'insensitive' } } }, + ]; + } + + const [data, total] = await Promise.all([ + this.prisma.pet.findMany({ + where, + skip, + take: limit, + orderBy: { createdAt: 'desc' }, + include: { + user: { + select: { id: true, firstName: true, lastName: true, mobile: true, email: true }, + }, + medicalConditions: true, + reminders: true, + healthLogs: { + orderBy: { loggedDate: 'desc' }, + take: 10, + }, + }, + }), + this.prisma.pet.count({ where }), + ]); + + return { + data, + meta: { total, page, limit, lastPage: Math.ceil(total / limit) }, + }; + } + async getDoctors() { return this.prisma.doctor.findMany({ orderBy: { createdAt: 'desc' }, diff --git a/backend/src/pets/pets.service.ts b/backend/src/pets/pets.service.ts index ecc22d5..b4410a4 100644 --- a/backend/src/pets/pets.service.ts +++ b/backend/src/pets/pets.service.ts @@ -31,6 +31,66 @@ export class PetsService { }); } + async findAllAdmin(filters: PaginationDto) { + const { + search, + page = 1, + limit = 10, + sortBy = 'createdAt', + sortOrder = 'desc', + } = filters; + + const whereClause: any = {}; + if (search) { + whereClause.OR = [ + { name: { contains: search, mode: 'insensitive' } }, + { breed: { contains: search, mode: 'insensitive' } }, + { user: { firstName: { contains: search, mode: 'insensitive' } } }, + { user: { lastName: { contains: search, mode: 'insensitive' } } }, + { user: { mobile: { contains: search } } }, + ]; + } + + const skip = (page - 1) * limit; + + const [data, total] = await Promise.all([ + this.prisma.pet.findMany({ + where: whereClause, + skip, + take: limit, + orderBy: { [sortBy]: sortOrder }, + include: { + user: { + select: { + id: true, + firstName: true, + lastName: true, + mobile: true, + email: true, + }, + }, + medicalConditions: true, + reminders: true, + healthLogs: { + orderBy: { loggedDate: 'desc' }, + take: 10, + }, + }, + }), + this.prisma.pet.count({ where: whereClause }), + ]); + + return { + data, + meta: { + total, + page, + lastPage: Math.ceil(total / limit), + limit, + }, + }; + } + async findAllByUser(userId: string, filters: PaginationDto) { const { search, @@ -56,6 +116,7 @@ export class PetsService { skip, take: limit, orderBy: { [sortBy]: sortOrder }, + include: { medicalConditions: true, reminders: true, healthLogs: true }, }), this.prisma.pet.count({ where: whereClause }), ]); diff --git a/docs/backlog.md b/docs/backlog.md index 5975516..320ff8e 100644 --- a/docs/backlog.md +++ b/docs/backlog.md @@ -214,7 +214,7 @@ - [x] **TASK-26.14 [Admin Wallet Adjust Endpoint]:** ایجاد اندپویینت `POST /api/users/:id/wallet-adjust` و فرم شارژ/کسر مستقیم کیف‌پول در پنل ادمین. - [x] **TASK-26.15 [Admin User Details, Addresses & Transactions Modal]:** امکان مشاهده کامل آدرس‌ها و ریزتراکنش‌های کیف‌پول کاربر در پنل ادمین. - [x] **TASK-26.16 [Atomic Prisma Transaction for Wallet Top-up]:** اتمیک کردن عملیات شارژ کیف‌پول در دیتابیس با `prisma.$transaction`. -- [ ] **TASK-26.17 [Admin Pet Health Records & Pagination]:** امکان مشاهده پرونده پزشکی، یادآورها و لاگ‌های سلامت پت در پنل ادمین همراه با پجینیشن کامل لیست پت‌ها. +- [x] **TASK-26.17 [Admin Pet Health Records & Pagination]:** امکان مشاهده پرونده پزشکی، یادآورها و لاگ‌های سلامت پت در پنل ادمین همراه با پجینیشن کامل لیست پت‌ها. - [ ] **TASK-26.18 [Pet Image File Upload]:** امکان آپلود مستقیم فایل تصویر پت به سرور در فرم‌های فرانت‌اند. - [ ] **TASK-26.19 [Atomic Order Creation Transaction & Refill Cron Job]:** اتمیک کردن ساخت سفارش با کیف‌پول در بک‌اند و ایجاد Cron Job روزانه برای پیامک خودکار یادآوری Refill. - [ ] **TASK-26.20 [Reports Excel Export, Dashboard Live Chart & Video View Counter]:** افزودن قابلیت دانلود خروجی اکسل در صفحه گزارشات، اتصال نمودار داشبورد به دیتای واقعی و ارسال درخواست PATCH افزایش بازدید ویدیوها.