diff --git a/src/app/modules/statictics/statictics.controller.ts b/src/app/modules/statictics/statictics.controller.ts new file mode 100644 index 0000000..667854e --- /dev/null +++ b/src/app/modules/statictics/statictics.controller.ts @@ -0,0 +1,92 @@ +import manageResponse from "../../utils/manage_response"; +import { Request, Response } from "express"; +import catchAsync from "../../utils/catch_async"; +import { statictics_service } from "./statictics.service"; + +const get_seller_stats = catchAsync(async (req: Request, res: Response) => { + const shopAccountId = req.user?.accountId; + if (!shopAccountId) { + return res.status(401).json({ + success: false, + message: "Unauthorized", + }); + } + const range = (req.query.range as "7d" | "30d" | "all") || "7d"; + + const result = await statictics_service.get_seller_stats_fromDb( + shopAccountId, + range, + ); + + manageResponse(res, { + success: true, + statusCode: 200, + message: "All statictics fetched successfully.", + data: result, + meta: {}, + }); +}); + +// const get_all_statictics = catchAsync(async (req, res) => { +// const result = await statictics_service.get_all_statictics_from_db(req); +// manageResponse(res, { +// success: true, +// statusCode: 200, +// message: "All statictics fetched successfully.", +// data: result, +// meta: {}, +// }); +// }); + +// const get_single_statictics = catchAsync(async (req, res) => { +// const result = await statictics_service.get_single_statictics_from_db(req); +// manageResponse(res, { +// success: true, +// statusCode: 200, +// message: "Single statictics fetched successfully.", +// data: result, +// meta: {}, +// }); +// }); + +// const create_statictics = catchAsync(async (req, res) => { +// const result = await statictics_service.create_statictics_into_db(req); +// manageResponse(res, { +// success: true, +// statusCode: 200, +// message: "statictics created successfully.", +// data: result, +// meta: {}, +// }); +// }); + +// const update_statictics = catchAsync(async (req, res) => { +// const result = await statictics_service.update_statictics_into_db(req); +// manageResponse(res, { +// success: true, +// statusCode: 200, +// message: "statictics updated successfully.", +// data: result, +// meta: {}, +// }); +// }); + +// const delete_statictics = catchAsync(async (req, res) => { +// const result = await statictics_service.delete_statictics_from_db(req); +// manageResponse(res, { +// success: true, +// statusCode: 200, +// message: "statictics deleted successfully.", +// data: result, +// meta: {}, +// }); +// }); + +export const statictics_controller = { + get_seller_stats, + // get_all_statictics, + // get_single_statictics, + // create_statictics, + // update_statictics, + // delete_statictics, +}; diff --git a/src/app/modules/statictics/statictics.route.ts b/src/app/modules/statictics/statictics.route.ts new file mode 100644 index 0000000..8ac6e22 --- /dev/null +++ b/src/app/modules/statictics/statictics.route.ts @@ -0,0 +1,24 @@ +import { Router } from "express"; +import auth from "../../middlewares/auth.js"; +import { statictics_controller } from "./statictics.controller.js"; +// import { statictics_controller } from "./statictics.controller"; +// import { statictics_validations } from "./statictics.validation"; + +const router = Router(); + +router.get("/seller", auth("USER"), statictics_controller.get_seller_stats); + +// router.post( +// "/", +// RequestValidator(statictics_validations.create_statictics), +// statictics_controller.create_statictics, +// ); +// router.get("/:id", statictics_controller.get_single_statictics); +// router.patch( +// "/:id", +// RequestValidator(statictics_validations.update_statictics), +// statictics_controller.update_statictics, +// ); +// router.delete("/:id", statictics_controller.delete_statictics); + +export const staticticsRoute = router; diff --git a/src/app/modules/statictics/statictics.service.ts b/src/app/modules/statictics/statictics.service.ts new file mode 100644 index 0000000..4112475 --- /dev/null +++ b/src/app/modules/statictics/statictics.service.ts @@ -0,0 +1,151 @@ +// import { Request } from "express"; +// import { prisma } from "../../lib/prisma"; + +import { prisma } from "../../lib/prisma"; + +type Range = "7d" | "30d" | "all"; + +const get_seller_stats_fromDb = async (shopAccountId: string, range: Range) => { + let createdAtFilter: any = {}; + + if (range !== "all") { + const days = range === "7d" ? 7 : 30; + const from = new Date(); + from.setDate(from.getDate() - days); + + createdAtFilter = { gte: from }; + } + + const baseWhere: any = { + shopAccountId, + ...(range !== "all" && { createdAt: createdAtFilter }), + }; + + const [ + totalOrders, + completedOrders, + pendingOrders, + rejectedOrders, + revenueResult, + last7DaysRejected, + ordersForChart, + ] = await Promise.all([ + prisma.order.count({ where: baseWhere }), + + prisma.order.count({ + where: { ...baseWhere, status: "DELIVERED" }, + }), + + prisma.order.count({ + where: { + ...baseWhere, + status: { in: ["INITIATED", "CONFIRMED", "ONGOING"] }, + }, + }), + + prisma.order.count({ + where: { ...baseWhere, status: "CANCELLED" }, + }), + + prisma.order.aggregate({ + where: { ...baseWhere, status: "DELIVERED" }, + _sum: { productPrice: true }, + }), + + prisma.order.count({ + where: { + shopAccountId, + status: "CANCELLED", + createdAt: { + gte: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), + }, + }, + }), + + range !== "all" + ? prisma.order.findMany({ + where: { + shopAccountId, + status: "DELIVERED", + createdAt: createdAtFilter, + }, + select: { + productPrice: true, + createdAt: true, + }, + }) + : Promise.resolve([]), + ]); + + const totalRevenue = revenueResult._sum.productPrice || 0; + + const avgOrderValue = + completedOrders > 0 ? totalRevenue / completedOrders : 0; + + const dailyMap: Record = {}; + + for (const o of ordersForChart) { + const date = o.createdAt.toISOString().split("T")[0]; + if (!dailyMap[date]) dailyMap[date] = 0; + dailyMap[date] += o.productPrice; + } + + const dailyRevenue = Object.entries(dailyMap).map(([date, revenue]) => ({ + date, + revenue, + })); + + return { + totalOrders, + completedOrders, + pendingOrders, + rejectedOrders, + totalRevenue, + avgOrderValue, + last7DaysRejected, + dailyRevenue, + }; +}; + +// const get_all_statictics_from_db = async (req: Request) => { +// // define your own login here +// const result = await prisma.statictics.findMany(); +// return result; +// }; + +// const get_single_statictics_from_db = async (req: Request) => { +// // define your own login here +// const { id } = req.params; +// const result = await prisma.statictics.findUnique({where:{id}}); +// return result; +// }; + +// const create_statictics_into_db = async (req: Request) => { +// // define your own login here +// const result = await prisma.statictics.create({data:req.body}); +// return result; +// }; + +// const update_statictics_into_db = async (req: Request) => { +// // define your own login here +// const { id } = req.params; +// const result = await prisma.statictics.update({where:{id},data:req.body}); +// return result; +// }; + +// const delete_statictics_from_db = async (req: Request) => { +// // define your own login here +// const { id } = req.params; +// const result = await prisma.statictics.delete({where:{id}}); +// return result; +// }; + +export const statictics_service = { + get_seller_stats_fromDb, + + // get_all_statictics_from_db, + // get_single_statictics_from_db, + // create_statictics_into_db, + // update_statictics_into_db, + // delete_statictics_from_db, +}; diff --git a/src/app/modules/statictics/statictics.swagger.ts b/src/app/modules/statictics/statictics.swagger.ts index 16a7ef0..b9f6f76 100644 --- a/src/app/modules/statictics/statictics.swagger.ts +++ b/src/app/modules/statictics/statictics.swagger.ts @@ -1,12 +1,11 @@ -export const staticticsSwaggerDocs = { + export const staticticsSwaggerDocs = { "/api/statictics": { post: { tags: ["statictics"], summary: "Create new statictics", description: "", requestBody: { - required: true, content: { "application/json": { @@ -45,33 +44,34 @@ export const staticticsSwaggerDocs = { }, "/api/statictics/seller": { get: { - tags: ["statistics"], - summary: "Get seller statistics", - description: "Fetch seller dashboard stats (7d, 30d, all)", + tags: ["statistics"], + summary: "Get seller statistics", + description: "Fetch seller dashboard stats (7d, 30d, all)", + + parameters: [ + { + name: "range", + in: "query", + required: false, + schema: { + type: "string", + enum: ["7d", "30d", "all"], + default: "7d", + }, + description: "Time range for statistics", + }, + ], - parameters: [ - { - name: "range", - in: "query", - required: false, - schema: { - type: "string", - enum: ["7d", "30d", "all"], - default: "7d", - }, - description: "Time range for statistics", - }, - ], - - responses: { - 200: { - description: "Statistics fetched successfully", - }, - 401: { - description: "Unauthorized", - }, + responses: { + 200: { + description: "Statistics fetched successfully", + }, + 401: { + description: "Unauthorized", }, }, + + }, }, "/api/statictics/{id}": { @@ -136,3 +136,6 @@ export const staticticsSwaggerDocs = { }, }, }; + + + diff --git a/src/app/modules/statictics/statictics.validation.ts b/src/app/modules/statictics/statictics.validation.ts new file mode 100644 index 0000000..81d229d --- /dev/null +++ b/src/app/modules/statictics/statictics.validation.ts @@ -0,0 +1,10 @@ + +import { z } from "zod"; + +const create_statictics = z.object({}); +const update_statictics = z.object({}); + +export const statictics_validations = { + create_statictics, + update_statictics, +}; diff --git a/src/app/modules/support/support.controller.ts b/src/app/modules/support/support.controller.ts index 7c4e8e5..3edf4a8 100644 --- a/src/app/modules/support/support.controller.ts +++ b/src/app/modules/support/support.controller.ts @@ -1,17 +1,14 @@ - import { Request, Response } from "express"; import catchAsync from "../../utils/catch_async.js"; import manageResponse from "../../utils/manage_response.js"; import { support_service } from "./support.service.js"; - - const createSupport = catchAsync(async (req: Request, res: Response) => { const id = req?.user?.accountId; const data = { ...req.body, - storeAccountId: id as string - } + storeAccountId: id as string, + }; const result = await support_service.createSupportIntoDB(data); manageResponse(res, { @@ -23,31 +20,43 @@ const createSupport = catchAsync(async (req: Request, res: Response) => { }); }); - const getAllSupport = catchAsync(async (req: Request, res: Response) => { const role = req?.user?.role; const id = req?.user?.accountId; const search = req?.query?.search; const type = req?.query?.type; const status = req?.query?.status; + const page = Number(req?.query?.page) || 1; + const limit = Number(req?.query?.limit) || 10; - const result = await support_service.getAllSupportFromDB(id as string, role as string, search as string, type as string, status as string); + const result = await support_service.getAllSupportFromDB( + id as string, + role as string, + search as string, + type as string, + status as string, + page, + limit + ); manageResponse(res, { success: true, statusCode: 200, message: "All support fetched successfully.", - data: result, - meta: {}, + data: result.data, + meta: result.meta, }); }); const get_single_support = catchAsync(async (req, res) => { - const {id} = req.params; + const { id } = req.params; const userId = req?.user?.accountId; - const role = req?.user?.role + const role = req?.user?.role; - - const result = await support_service.getSingleSupportFromDB(id as string, userId as string, role as string); + const result = await support_service.getSingleSupportFromDB( + id as string, + userId as string, + role as string, + ); manageResponse(res, { success: true, statusCode: 200, @@ -57,15 +66,18 @@ const get_single_support = catchAsync(async (req, res) => { }); }); - const update_support = catchAsync(async (req, res) => { - const {id} = req.params; + const { id } = req.params; const userId = req?.user?.accountId; const role = req?.user?.role; - const data = req.body + const data = req.body; - - const result = await support_service.updateSupportIntoDB(id as string, userId as string, role as string, data); + const result = await support_service.updateSupportIntoDB( + id as string, + userId as string, + role as string, + data, + ); manageResponse(res, { success: true, statusCode: 200, @@ -76,11 +88,15 @@ const update_support = catchAsync(async (req, res) => { }); const delete_support = catchAsync(async (req, res) => { - const {id} = req.params; + const { id } = req.params; const userId = req?.user?.accountId; - const role = req?.user?.role + const role = req?.user?.role; - const result = await support_service.deleteSupportFromDB(id as string, userId as string, role as string); + const result = await support_service.deleteSupportFromDB( + id as string, + userId as string, + role as string, + ); manageResponse(res, { success: true, statusCode: 200, diff --git a/src/app/modules/support/support.service.ts b/src/app/modules/support/support.service.ts index 43e2e0b..41a22e4 100644 --- a/src/app/modules/support/support.service.ts +++ b/src/app/modules/support/support.service.ts @@ -13,6 +13,8 @@ const getAllSupportFromDB = async ( search?: string, type?: string, status?: string, + page: number = 1, + limit: number = 10, ) => { const andCondition: Prisma.SupportWhereInput[] = []; @@ -55,14 +57,31 @@ const getAllSupportFromDB = async ( const whereCondition: Prisma.SupportWhereInput = andCondition.length > 0 ? { AND: andCondition } : {}; - const result = await prisma.support.findMany({ - where: whereCondition, - orderBy: { - createdAt: "desc", - }, - }); + const skip = (page - 1) * limit; - return result; + const [data, total] = await Promise.all([ + prisma.support.findMany({ + where: whereCondition, + skip, + take: limit, + orderBy: { + createdAt: "desc", + }, + }), + prisma.support.count({ + where: whereCondition, + }), + ]); + + return { + meta: { + page, + limit, + total, + totalPage: Math.ceil(total / limit), + }, + data, + }; }; const getSingleSupportFromDB = async ( @@ -125,8 +144,8 @@ const deleteSupportFromDB = async ( } const result = await prisma.support.delete({ - where: {id} - }) + where: { id }, + }); return result; }; diff --git a/src/app/modules/support/support.swagger.ts b/src/app/modules/support/support.swagger.ts index a4fe40d..8c5ab4b 100644 --- a/src/app/modules/support/support.swagger.ts +++ b/src/app/modules/support/support.swagger.ts @@ -1,18 +1,18 @@ - - export const supportSwaggerDocs = { +export const supportSwaggerDocs = { "/api/support": { post: { tags: ["support"], summary: "Create new support", - description: "", + description: + "type must be: TECHNICAL | BILLING | DOMAIN | TEMPLATE | PAYMENT | ACCOUNT | FEATURE_REQUEST | BUG | OTHER", requestBody: { required: true, content: { "application/json": { example: JSON.stringify({ - "issueName": "Your issue name", - "description": "Issue description", - "type": "Issue Type" + issueName: "Your issue name", + description: "Issue description", + type: "Issue Type", }), // put your request body }, }, @@ -39,6 +39,45 @@ required: false, schema: { type: "number" }, }, + { + name: "search", + in: "query", + required: false, + description: "Search by issue name or description", + schema: { + type: "string", + }, + }, + { + name: "type", + in: "query", + required: false, + description: "Filter by support type", + schema: { + type: "string", + enum: [ + "TECHNICAL", + "BILLING", + "DOMAIN", + "TEMPLATE", + "PAYMENT", + "ACCOUNT", + "FEATURE_REQUEST", + "BUG", + "OTHER", + ], + }, + }, + { + name: "status", + in: "query", + required: false, + description: "Filter by support status", + schema: { + type: "string", + enum: ["OPEN", "IN_PROGRESS", "RESOLVED", "REJECTED"], + }, + }, ], responses: { 200: { description: "support fetched successfully" }, @@ -68,7 +107,11 @@ patch: { tags: ["support"], summary: "Update support", - description: "", + description: `type(enum): TECHNICAL | BILLING | DOMAIN | TEMPLATE | PAYMENT | ACCOUNT | FEATURE_REQUEST | BUG | OTHER \n + status(enum): OPEN + | IN_PROGRESS + | RESOLVED + | REJECTED`, parameters: [ { name: "id", @@ -81,7 +124,14 @@ required: true, content: { "application/json": { - example: JSON.stringify({}), // put your request body + example: JSON.stringify({ + issueName: "Your issue name", + description: "Issue description", + type: "Issue Type", + status: "issue current status", + resolvedBy: "dataTime()", + resolvedAt: "dateTime()", + }), // put your request body }, }, }, @@ -109,6 +159,3 @@ }, }, }; - - - \ No newline at end of file diff --git a/src/routes.ts b/src/routes.ts index faf8148..ab69280 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -4,12 +4,14 @@ import orderRoute from "./app/modules/order/order.route.js"; import planRoute from "./app/modules/plan/plan.route.js"; import profileRoute from "./app/modules/profile/profile.route.js"; import supportRoute from "./app/modules/support/support.route.js"; -import templateRoute from "./app/modules/template/template.route"; +import templateRoute from "./app/modules/template/template.route.js"; +import { staticticsRoute } from "./app/modules/statictics/statictics.route.js"; const appRouter = Router(); const moduleRoutes = [ { path: "/template", route: templateRoute }, + { path: "/statictics", route: staticticsRoute }, { path: "/order", route: orderRoute }, { path: "/support", route: supportRoute }, { path: "/plan", route: planRoute }, diff --git a/src/swaggerOptions.ts b/src/swaggerOptions.ts index 3aa0273..d317fed 100644 --- a/src/swaggerOptions.ts +++ b/src/swaggerOptions.ts @@ -6,7 +6,8 @@ import { orderSwaggerDocs } from "./app/modules/order/order.swagger.js"; import { planSwaggerDocs } from "./app/modules/plan/plan.swagger.js"; import { profileSwaggerDocs } from "./app/modules/profile/profile.swagger.js"; import { supportSwaggerDocs } from "./app/modules/support/support.swagger.js"; -import { templateSwaggerDocs } from "./app/modules/template/template.swagger"; +import { templateSwaggerDocs } from "./app/modules/template/template.swagger.js"; +import { staticticsSwaggerDocs } from "./app/modules/statictics/statictics.swagger.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -26,6 +27,7 @@ export const swaggerOptions = { ...profileSwaggerDocs, ...supportSwaggerDocs, ...templateSwaggerDocs, + ...staticticsSwaggerDocs, }, servers: configs.env === "production"