diff --git a/src/api/marketplace/orders/serializers.py b/src/api/marketplace/orders/serializers.py index f0e83c4e..ee65543e 100644 --- a/src/api/marketplace/orders/serializers.py +++ b/src/api/marketplace/orders/serializers.py @@ -25,12 +25,20 @@ class Meta: model = OrderItemMetaData fields = '__all__' + +class OrderReadSerializer(serializers.ModelSerializer): + + class Meta: + model = Order + fields = '__all__' + # Serializer for the GET details of an order class OrderItemReadSerializer(serializers.ModelSerializer): package = PackageSerializer(read_only=True) service_master = ServiceMasterReadSerializer(read_only=True) currency = CurrencySerializer(read_only=True) order_item_meta_data = serializers.SerializerMethodField() + order_id = OrderReadSerializer(read_only=True) class Meta: model = OrderItem @@ -70,6 +78,30 @@ class OrderListFilterSerializer(serializers.Serializer): search = serializers.CharField(required=False, allow_blank=True) +class OrderItemListFilterSerializer(serializers.Serializer): + influencers = serializers.ListField( + child=serializers.UUIDField(), required=False) + buyers = serializers.ListField( + child=serializers.UUIDField(), required=False) + status = serializers.ListField( + child=serializers.CharField(), required=False) + service_masters = serializers.ListField( + child=serializers.UUIDField(), required=False + ) + order_ids = serializers.ListField( + child=serializers.UUIDField(), required=False) + lt_created_at = serializers.DateTimeField(required=False) + gt_created_at = serializers.DateTimeField(required=False) + lt_rating = serializers.FloatField(required=False) + gt_rating = serializers.FloatField(required=False) + lt_amount = serializers.FloatField(required=False) + gt_amount = serializers.FloatField(required=False) + order_by = serializers.CharField(required=False) + search = serializers.CharField(required=False, allow_blank=True) + lt_publish_date = serializers.DateTimeField(required=False) + gt_publish_date = serializers.DateTimeField(required=False) + + # The response schema for the list of orders from the POST search request class OrderSerializer(serializers.ModelSerializer): buyer = UserSerializer(read_only=True) diff --git a/src/api/marketplace/orders/views.py b/src/api/marketplace/orders/views.py index 728cc94e..e00fedfb 100644 --- a/src/api/marketplace/orders/views.py +++ b/src/api/marketplace/orders/views.py @@ -26,6 +26,8 @@ from .serializers import ( CreateOrderMessageSerializer, CreateOrderSerializer, + OrderItemListFilterSerializer, + OrderItemReadSerializer, OrderListFilterSerializer, OrderSerializer, OrderItemSerializer, @@ -42,6 +44,12 @@ from rest_framework import status from django.db.models import Q from django.utils import timezone +from django.db.models import F, ExpressionWrapper, DateTimeField, Case, When, BooleanField +from django.db.models.functions import Now +from django.db.models import Min + + + # ORDER API-Endpoint @@ -153,9 +161,6 @@ def post(self, request): if "buyers" in filters: orders = orders.filter(buyer__in=filters["buyers"]) - if "status" in filters: - orders = orders.filter(status__in=filters["status"]) - if "service_masters" in filters: orders = orders.filter( order_item_order_id__service_master__in=filters["service_masters"] @@ -192,16 +197,52 @@ def post(self, request): | Q(order_code__icontains=filters["search"]) ) - if "order_by" in filters: + if "order_by" in filters and filters["order_by"] == "upcoming": + # Order by the publish date of the order items + # Should sort the publish_date closest to the current date first wrt the order + orders = orders.annotate( + min_publish_date=Min('order_item_order_id__publish_date'), + time_difference=ExpressionWrapper( + F('min_publish_date') - Now(), output_field=DateTimeField() + ), + is_future=Case( + When(min_publish_date__gte=Now(), then=True), + default=False, + output_field=BooleanField(), + ) + ).order_by( + '-is_future', + Case( + When(is_future=True, then=F('time_difference')), + When(is_future=False, then=F('time_difference') * -1), + output_field=DateTimeField(), + ) + ) + elif "order_by" in filters: orders = orders.order_by(filters["order_by"]) + status_counts = { + "accepted": orders.filter(status="accepted").count(), + "pending": orders.filter(status="pending").count(), + "completed": orders.filter(status="completed").count(), + "rejected": orders.filter(status="rejected").count(), + "cancelled": orders.filter(status="cancelled").count(), + } + + if "status" in filters: + orders = orders.filter(status__in=filters["status"]) + pagination = Pagination(orders, request) serializer = OrderSerializer(pagination.getData(), context={ "request": request}, many=True) + combined_data = { + "orders": serializer.data, + "status_counts": status_counts, + } return Response( { "isSuccess": True, - "data": serializer.data, + "data": combined_data, "message": "All Order retrieved successfully", "pagination": pagination.getPageInfo(), }, @@ -684,11 +725,13 @@ def post(self, request): # ORDER-Item API-Endpoint # List-Create-API class OrderItemList(APIView): + authentication_classes = [JWTAuthentication] def get(self, request): try: orderItems = OrderItem.objects.all() pagination = Pagination(orderItems, request) - serializer = OrderItemSerializer(pagination.getData(), many=True) + serializer = OrderItemReadSerializer( + pagination.getData(), many=True) return Response( { "isSuccess": True, @@ -701,22 +744,113 @@ def get(self, request): except Exception as e: return handleServerException(e) - @swagger_auto_schema(request_body=OrderItemSerializer) + @swagger_auto_schema(request_body=OrderItemListFilterSerializer) def post(self, request): try: - serializer = OrderItemSerializer(data=request.data) - if serializer.is_valid(): - serializer.save() - return Response( - { - "isSuccess": True, - "data": OrderItemSerializer(serializer.instance).data, - "message": "Order Item created successfully", - }, - status=status.HTTP_201_CREATED, + filter_serializer = OrderItemListFilterSerializer( + data=request.data) + filter_serializer.is_valid(raise_exception=True) + filters = filter_serializer.validated_data + + user = request.user_account + role = request.user_account.role + if role.name == "business_owner": + orderItems = OrderItem.objects.filter( + Q(order_id__buyer=user), deleted_at=None + ).distinct() + elif role.name == "influencer": + # For all the order items, there will be a package in it and the package willl have influencer id + orderItems = OrderItem.objects.filter( + Q(package__influencer=user), deleted_at=None + ).distinct() + + if "service_masters" in filters: + orderItems = orderItems.filter( + service_master__in=filters["service_masters"] ) - else: - return handleBadRequest(serializer.errors) + + if "gt_created_at" in filters: + gt_created_at = filters["gt_created_at"].date() + orderItems = orderItems.filter( + created_at__date__gte=gt_created_at) + + if "lt_created_at" in filters: + lt_created_at = filters["lt_created_at"].date() + orderItems = orderItems.filter( + created_at__date__lte=lt_created_at) + + if "lt_rating" in filters: + orderItems = orderItems.filter( + review__rating__lt=filters["lt_rating"]) + + if "gt_rating" in filters: + orderItems = orderItems.filter( + review__rating__gt=filters["gt_rating"]) + + if "search" in filters: + orderItems = orderItems.filter( + Q(order_id__buyer__first_name__icontains=filters["search"]) + | Q(order_id__buyer__last_name__icontains=filters["search"]) + | Q(package__influencer__first_name__icontains=filters["search"]) + | Q(package__influencer__last_name__icontains=filters["search"]) + | Q(order_id__order_code__icontains=filters["search"]) + ) + + if "buyers" in filters: + orderItems = orderItems.filter( + order_id__buyer__in=filters["buyers"]) + + if "order_by" in filters and filters["order_by"] == "upcoming": + orderItems = orderItems.annotate( + time_difference=ExpressionWrapper( + F('publish_date') - Now(), output_field=DateTimeField() + ), + is_future=Case( + When(publish_date__gte=Now(), then=True), + default=False, + output_field=BooleanField(), + ) + ).order_by( + '-is_future', + Case( + When(is_future=True, then=F('time_difference')), + When(is_future=False, then=F('time_difference') * -1), + output_field=DateTimeField(), + ) + ) + elif "order_by" in filters: + orderItems = orderItems.order_by(filters["order_by"]) + + # Attach the total count of each status + status_counts = { + "accepted": orderItems.filter(status="accepted").count(), + "published": orderItems.filter(status="published").count(), + "cancelled": orderItems.filter(status="cancelled").count(), + "scheduled": orderItems.filter(status="scheduled").count(), + "rejected": orderItems.filter(status="rejected").count(), + } + + if "status" in filters: + orderItems = orderItems.filter(status__in=filters["status"]) + + pagination = Pagination(orderItems, request) + serializer = OrderItemReadSerializer( + pagination.getData(), context={"request": request}, many=True) + + combined_data = { + "order_items": serializer.data, + "status_counts": status_counts, + } + + return Response( + { + "isSuccess": True, + "data": combined_data, + "message": "All Order Items retrieved successfully", + "pagination": pagination.getPageInfo(), + }, + status=status.HTTP_200_OK, + ) except Exception as e: return handleServerException(e) diff --git a/src/ui/app/business/dashboard/page.tsx b/src/ui/app/business/dashboard/page.tsx index 3756fa80..1c430508 100644 --- a/src/ui/app/business/dashboard/page.tsx +++ b/src/ui/app/business/dashboard/page.tsx @@ -62,6 +62,7 @@ export default function BusinessDashboardPage() { ORDER_STATUS.REJECTED, ORDER_STATUS.PENDING, ORDER_STATUS.COMPLETED, + ORDER_STATUS.CANCELLED, ], order_by: "-created_at", }); @@ -94,7 +95,14 @@ export default function BusinessDashboardPage() { } ); if (isSuccess) { - setOrders(data?.data); + setOrders(data?.data?.orders); + setOrderCount({ + accepted: data?.data?.status_counts?.accepted, + completed: data?.data?.status_counts?.completed, + pending: data?.data?.status_counts?.pending, + rejected: data?.data?.status_counts?.rejected, + cancelled: data?.data?.status_counts?.cancelled, + }); setPagination({ ...pagination, total_data_count: data?.pagination?.total_data_count, @@ -108,28 +116,6 @@ export default function BusinessDashboardPage() { } }; - const getOrdersCount = async () => { - try { - setLoading(true); - const { isSuccess, data, message } = await getService( - `orders/order-list/` - ); - if (isSuccess) { - setOrderCount({ - accepted: data?.data?.accepted, - completed: data?.data?.completed, - pending: data?.data?.pending, - rejected: data?.data?.rejected, - cancelled: data?.data?.cancelled, - }); - } else { - notification(message ? message : "Something went wrong", "error"); - } - } finally { - setLoading(false); - } - }; - const cancelOrder = async (id: string) => { const { isSuccess, data, message } = await putService( `/orders/cancel-order/${id}/`, @@ -582,10 +568,6 @@ export default function BusinessDashboardPage() { }, ]; - useEffect(() => { - getOrdersCount(); - }, []); - useEffect(() => { const delayDebounceFn = setTimeout(() => { getOrders(); @@ -619,6 +601,25 @@ export default function BusinessDashboardPage() { card={card} selectedCard={selectedCard} orderCount={orderCount} + count={ + card?.value === 0 + ? orderCount?.accepted + + orderCount?.completed + + orderCount?.pending + + orderCount?.rejected + + orderCount?.cancelled + : card?.value === 1 + ? orderCount?.accepted + : card?.value === 2 + ? orderCount?.completed + : card?.value === 3 + ? orderCount?.pending + : card?.value === 4 + ? orderCount?.rejected + : card?.value === 5 + ? orderCount?.cancelled + : 0 + } /> ); @@ -651,7 +652,7 @@ export default function BusinessDashboardPage() { ? model?.[0]?.sort === "asc" ? `-${model?.[0]?.field}` : `${model?.[0]?.field}` - : undefined, + : "upcoming", })); }} localeText={{ diff --git a/src/ui/app/influencer/dashboard/page.tsx b/src/ui/app/influencer/dashboard/page.tsx index 2d37340a..8e4c5b30 100644 --- a/src/ui/app/influencer/dashboard/page.tsx +++ b/src/ui/app/influencer/dashboard/page.tsx @@ -1,28 +1,31 @@ "use client"; +import BackIcon from "@/public/svg/Back.svg"; import AcceptedOrders from "@/public/svg/acceptedOrders.svg?icon"; import CompletedOrders from "@/public/svg/completedOrders.svg?icon"; import RejectedOrders from "@/public/svg/rejectedOrders.svg?icon"; import TotalOrders from "@/public/svg/totalOrders.svg?icon"; import FilterBar from "@/src/components/dashboardComponents/filtersBar"; -import OrderDetails from "@/src/components/dashboardComponents/orderDetails"; +import ReviewModal from "@/src/components/dashboardComponents/reviewModal"; import StatusCard from "@/src/components/dashboardComponents/statusCard"; import TransactionIcon from "@/src/components/dashboardComponents/transactionIcon"; +import UpdateOrder from "@/src/components/dashboardComponents/updateOrder"; import { notification } from "@/src/components/shared/notification"; import RouteProtection from "@/src/components/shared/routeProtection"; import StatusChip from "@/src/components/shared/statusChip"; import ClaimEscrow from "@/src/components/web3Components/claimEscrow"; -import { getService, postService } from "@/src/services/httpServices"; -import Image from "next/image"; -import BackIcon from "@/public/svg/Back.svg"; +import { postService } from "@/src/services/httpServices"; import { DISPLAY_DATE_FORMAT, + DISPLAY_DATE_TIME_FORMAT, ORDER_ITEM_STATUS, ORDER_STATUS, TRANSACTION_TYPE, } from "@/src/utils/consts"; +import CancelScheduleSendIcon from "@mui/icons-material/CancelScheduleSend"; import EditNoteIcon from "@mui/icons-material/EditNote"; import OpenInNewIcon from "@mui/icons-material/OpenInNew"; +import ScheduleSendIcon from "@mui/icons-material/ScheduleSend"; import { Badge, Box, @@ -31,6 +34,8 @@ import { Link, Pagination, Rating, + Tab, + Tabs, Tooltip, Typography, } from "@mui/material"; @@ -40,18 +45,35 @@ import { GridTreeNodeWithRender, } from "@mui/x-data-grid"; import dayjs from "dayjs"; +import Image from "next/image"; import NextLink from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; import React, { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; -import ReviewModal from "@/src/components/dashboardComponents/reviewModal"; +const tabs = [ + { + title: "Orders", + route: `/influencer/dashboard/?tab=orders`, + value: 0, + key: "orders", + }, + { + title: "Order Items", + route: `/influencer/dashboard/?tab=order-items`, + value: 1, + key: "order-items", + }, +]; export default function BusinessDashboardPage() { const router = useRouter(); + const searchParams = useSearchParams(); const [selectedOrder, setSelectedOrder] = useState(null); const [loading, setLoading] = useState(false); const [orders, setOrders] = useState([]); + const [orderItems, setOrderItems] = useState([]); const [selectedReviewOrder, setSelectedReviewOrder] = useState(null); + const [open, setOpen] = useState(false); const [openReviewModal, setOpenReviewModal] = useState(false); const [selectedCard, setSelectedCard] = React.useState(0); const [filters, setFilters] = React.useState({ @@ -59,6 +81,7 @@ export default function BusinessDashboardPage() { ORDER_STATUS.ACCEPTED, ORDER_STATUS.REJECTED, ORDER_STATUS.COMPLETED, + ORDER_STATUS.CANCELLED, ], order_by: "-created_at", }); @@ -69,12 +92,43 @@ export default function BusinessDashboardPage() { rejected: 0, cancelled: 0, }); + const [orderItemsCount, setOrderItemsCount] = React.useState({ + accepted: 0, + scheduled: 0, + published: 0, + rejected: 0, + cancelled: 0, + }); const [pagination, setPagination] = React.useState({ total_data_count: 0, total_page_count: 0, current_page_number: 1, current_page_size: 10, }); + const [selectedTab, setSelectedTab] = React.useState(0); + + const schedulePost = async (id: string, action: string) => { + try { + const { isSuccess, data, message } = await postService( + action === ORDER_ITEM_STATUS.SCHEDULED + ? `/orders/send-tweet` + : `/orders/cancel-tweet`, + { + order_item_id: id, + } + ); + if (isSuccess) { + notification(message); + getOrderItems(); + } else { + notification( + message ? message : "Something went wrong, try again later", + "error" + ); + } + } finally { + } + }; const getOrders = async () => { try { @@ -88,21 +142,19 @@ export default function BusinessDashboardPage() { } ); if (isSuccess) { - setOrders(data?.data); + setOrders(data?.data?.orders); + setOrderCount({ + accepted: data?.data?.status_counts?.accepted, + completed: data?.data?.status_counts?.completed, + pending: data?.data?.status_counts?.pending, + rejected: data?.data?.status_counts?.rejected, + cancelled: data?.data?.status_counts?.cancelled, + }); setPagination({ ...pagination, total_data_count: data?.pagination?.total_data_count, total_page_count: data?.pagination?.total_page_count, }); - - // To update the drawer component when updating status. - if (selectedOrder) { - setSelectedOrder( - data?.data?.find( - (item: OrderType) => item?.id === selectedOrder?.id - ) - ); - } } else { notification(message ? message : "Something went wrong", "error"); } @@ -111,19 +163,30 @@ export default function BusinessDashboardPage() { } }; - const getOrdersCount = async () => { + const getOrderItems = async () => { try { setLoading(true); - const { isSuccess, data, message } = await getService( - `orders/order-list/` + const { isSuccess, data, message } = await postService( + `/orders/order-item/`, + { + page_number: pagination.current_page_number, + page_size: pagination.current_page_size, + ...filters, + } ); if (isSuccess) { - setOrderCount({ - accepted: data?.data?.accepted, - completed: data?.data?.completed, - pending: data?.data?.pending, - rejected: data?.data?.rejected, - cancelled: data?.data?.cancelled, + setOrderItems(data?.data?.order_items); + setOrderItemsCount({ + accepted: data?.data?.status_counts?.accepted, + scheduled: data?.data?.status_counts?.scheduled, + published: data?.data?.status_counts?.published, + rejected: data?.data?.status_counts?.rejected, + cancelled: data?.data?.status_counts?.cancelled, + }); + setPagination({ + ...pagination, + total_data_count: data?.pagination?.total_data_count, + total_page_count: data?.pagination?.total_page_count, }); } else { notification(message ? message : "Something went wrong", "error"); @@ -153,6 +216,7 @@ export default function BusinessDashboardPage() { ORDER_STATUS.ACCEPTED, ORDER_STATUS.REJECTED, ORDER_STATUS.COMPLETED, + ORDER_STATUS.CANCELLED, ], })); setPagination((prev) => ({ @@ -260,6 +324,148 @@ export default function BusinessDashboardPage() { }, ]; + const orderItemStatusCards = [ + { + label: "All", + onClick: () => { + setFilters((prev) => ({ + ...prev, + status: [ + ORDER_ITEM_STATUS.IN_PROGRESS, + ORDER_ITEM_STATUS.CANCELLED, + ORDER_ITEM_STATUS.REJECTED, + ORDER_ITEM_STATUS.ACCEPTED, + ORDER_ITEM_STATUS.SCHEDULED, + ORDER_ITEM_STATUS.PUBLISHED, + ], + })); + setPagination((prev) => ({ + ...prev, + current_page_number: 1, + })); + setSelectedCard(0); + }, + value: 0, + icon: ( + + ), + }, + { + label: "Accepted", + onClick: () => { + setFilters((prev) => ({ + ...prev, + status: [ORDER_ITEM_STATUS.ACCEPTED], + })); + setPagination((prev) => ({ + ...prev, + current_page_number: 1, + })); + setSelectedCard(1); + }, + value: 1, + icon: ( + + ), + }, + { + label: "Scheduled", + onClick: () => { + setFilters((prev) => ({ + ...prev, + status: [ORDER_ITEM_STATUS.SCHEDULED], + })); + setPagination((prev) => ({ + ...prev, + current_page_number: 1, + })); + setSelectedCard(2); + }, + value: 2, + icon: ( + + ), + }, + { + label: "Published", + onClick: () => { + setFilters((prev) => ({ + ...prev, + status: [ORDER_ITEM_STATUS.PUBLISHED], + })); + setPagination((prev) => ({ + ...prev, + current_page_number: 1, + })); + setSelectedCard(3); + }, + value: 3, + icon: ( + + ), + }, + { + label: "Rejected", + onClick: () => { + setFilters((prev) => ({ + ...prev, + status: [ORDER_ITEM_STATUS.REJECTED], + })); + setPagination((prev) => ({ + ...prev, + current_page_number: 1, + })); + setSelectedCard(4); + }, + value: 4, + icon: ( + + ), + }, + { + label: "Cancelled", + onClick: () => { + setFilters((prev) => ({ + ...prev, + status: [ORDER_ITEM_STATUS.CANCELLED], + })); + setPagination((prev) => ({ + ...prev, + current_page_number: 1, + })); + setSelectedCard(5); + }, + value: 5, + icon: ( + + ), + }, + ]; + const columns = [ { field: "order_code", @@ -406,6 +612,7 @@ export default function BusinessDashboardPage() { { setSelectedOrder(params?.row); + setOpen(true); }} > @@ -537,17 +744,231 @@ export default function BusinessDashboardPage() { }, ]; + const orderItemColumns = [ + // Columns for Package name, Price, Order Item Creation Date, Publish Date, Order Code, Published Tweet Link, Status + { + field: "package__name", + headerName: "Service", + flex: 1, + minWidth: 200, + sortable: false, + renderCell: ( + params: GridRenderCellParams + ): React.ReactNode => { + return ( + + {params?.row?.package?.name} + + ); + }, + }, + { + field: "order_id__order_code", + headerName: "Order", + flex: 1, + renderCell: ( + params: GridRenderCellParams + ): React.ReactNode => { + return ( + + {params?.row?.order_id?.order_code} + + ); + }, + }, + { + field: "price", + headerName: "Price", + flex: 1, + renderCell: ( + params: GridRenderCellParams + ): React.ReactNode => { + return ( + + {params?.row?.price} {params?.row?.currency?.symbol} + + ); + }, + }, + { + field: "publish_date", + headerName: "Publish Date & Time", + flex: 1, + renderCell: ( + params: GridRenderCellParams + ): React.ReactNode => { + return ( + + {params?.row?.publish_date + ? dayjs(params?.row?.publish_date).format( + DISPLAY_DATE_TIME_FORMAT + ) + : "Not Published"} + + ); + }, + }, + { + field: "published_tweet_id", + headerName: "Published Post Link", + flex: 1, + sortable: false, + renderCell: ( + params: GridRenderCellParams + ): React.ReactNode => { + return ( + + {params?.row?.published_tweet_id ? ( + + + + + + ) : ( + + Not Published + + )} + + ); + }, + }, + { + field: "actions", + headerName: "Actions", + flex: 1, + sortable: false, + renderCell: ( + params: GridRenderCellParams + ): React.ReactNode => { + return ( + + {params?.row?.status === ORDER_ITEM_STATUS.ACCEPTED && + // Publish date is in the future + dayjs(params?.row?.publish_date) > dayjs() && ( + + { + schedulePost( + params?.row?.id, + ORDER_ITEM_STATUS.SCHEDULED + ); + }} + > + + + + )} + {params?.row?.status === ORDER_ITEM_STATUS.SCHEDULED && + dayjs(params?.row?.publish_date) > dayjs() && ( + + { + schedulePost( + params?.row?.id, + ORDER_ITEM_STATUS.CANCELLED + ); + }} + > + + + + )} + + ); + }, + }, + { + field: "status", + headerName: "Status", + flex: 1, + renderCell: ( + params: GridRenderCellParams + ): React.ReactNode => { + return ; + }, + }, + ]; + useEffect(() => { - getOrdersCount(); - }, []); + const tab = searchParams.get("tab"); + const _selectedTab = tabs.find((_tab) => _tab.key === tab); + if (_selectedTab) setSelectedTab(_selectedTab?.value); + }, [searchParams]); useEffect(() => { const delayDebounceFn = setTimeout(() => { - getOrders(); + if (selectedTab === 0) { + getOrders(); + } + if (selectedTab === 1) { + getOrderItems(); + } }, 500); return () => clearTimeout(delayDebounceFn); }, [pagination.current_page_number, pagination.current_page_size, filters]); + useEffect(() => { + if (selectedTab === 1) { + setFilters((prev) => ({ + ...prev, + status: [ + ORDER_ITEM_STATUS.ACCEPTED, + ORDER_ITEM_STATUS.PUBLISHED, + ORDER_ITEM_STATUS.REJECTED, + ORDER_ITEM_STATUS.SCHEDULED, + ORDER_ITEM_STATUS.CANCELLED, + ORDER_ITEM_STATUS.IN_PROGRESS, + ], + order_by: "upcoming", + })); + setSelectedCard(0); + } else { + setFilters((prev) => ({ + ...prev, + status: [ + ORDER_STATUS.ACCEPTED, + ORDER_STATUS.REJECTED, + ORDER_STATUS.COMPLETED, + ], + order_by: "upcoming", + })); + setSelectedCard(0); + } + }, [selectedTab]); + return ( - {"BackIcon"} { - router.back(); - }} - /> - - - {statusCards.map((card, index) => { + + {"BackIcon"} { + router.back(); + }} + /> + { + setSelectedTab(newValue); + router.push(tabs.find((tab) => tab.value === newValue)?.route!); + }} + > + {tabs.map((tab, index) => { return ( - - - + ); })} + + + + + + + + {selectedTab === 0 ? ( + <> + {statusCards.map((card, index) => { + return ( + + + + ); + })} + + ) : ( + <> + {orderItemStatusCards.map((card, index) => { + return ( + + + + ); + })} + + )} @@ -589,8 +1109,8 @@ export default function BusinessDashboardPage() { getRowId={(row) => (row?.id ? row?.id : 0)} autoHeight loading={loading} - rows={orders} - columns={columns} + rows={selectedTab === 0 ? orders : orderItems} + columns={selectedTab === 0 ? columns : orderItemColumns} disableRowSelectionOnClick disableColumnFilter hideFooter @@ -606,7 +1126,7 @@ export default function BusinessDashboardPage() { ? model?.[0]?.sort === "asc" ? `-${model?.[0]?.field}` : `${model?.[0]?.field}` - : undefined, + : "upcoming", })); }} localeText={{ @@ -637,12 +1157,10 @@ export default function BusinessDashboardPage() { - { - setSelectedOrder(null); - }} - getOrders={getOrders} + ([]); useEffect(() => { if (formFields?.value) { @@ -79,6 +77,7 @@ export default function ArrayItem({ { + let apiEndpoint = ""; + if (action === ORDER_ITEM_STATUS.SCHEDULED) + apiEndpoint = "orders/send-tweet"; + if (action === ORDER_ITEM_STATUS.CANCELLED) + apiEndpoint = "orders/cancel-tweet"; + + try { + const { isSuccess, data, message } = await postService(apiEndpoint, { + order_item_id: orderItem?.order_item?.id, + }); + if (isSuccess) { + notification(message); + // Once any item is published or cancelled, update the orders data + } else { + notification( + message ? message : "Something went wrong, try again later", + "error" + ); + } + } finally { + } + }; + /** * React useEffect hook that updates the publish date of an order item or a specific index. * @@ -249,6 +277,31 @@ export default function OrderItemForm({ } /> )} + {orderItem?.order_item?.status === ORDER_ITEM_STATUS.ACCEPTED && + // Publish date is in the future + dayjs(orderItem?.order_item?.publish_date) > dayjs() && ( + + { + updateStatus(ORDER_ITEM_STATUS.SCHEDULED); + }} + > + + + + )} + {orderItem?.order_item?.status === ORDER_ITEM_STATUS.SCHEDULED && + dayjs(orderItem?.order_item?.publish_date) > dayjs() && ( + + { + updateStatus(ORDER_ITEM_STATUS.CANCELLED); + }} + > + + + + )} { if ( updateFunction && diff --git a/src/ui/src/components/dashboardComponents/statusCard/index.tsx b/src/ui/src/components/dashboardComponents/statusCard/index.tsx index 4fcce084..2757a6ca 100644 --- a/src/ui/src/components/dashboardComponents/statusCard/index.tsx +++ b/src/ui/src/components/dashboardComponents/statusCard/index.tsx @@ -2,7 +2,6 @@ import { Box, Typography } from "@mui/material"; import React from "react"; -import Image from "next/image"; type StatusCardProps = { card: { @@ -19,12 +18,14 @@ type StatusCardProps = { rejected: number; cancelled: number; }; + count: number; }; export default function StatusCard({ card, selectedCard, orderCount, + count, }: StatusCardProps) { return ( - {card?.value === 0 - ? orderCount?.accepted + - orderCount?.completed + - orderCount?.pending + - orderCount?.rejected - : card?.value === 1 - ? orderCount?.accepted - : card?.value === 2 - ? orderCount?.completed - : card?.value === 3 - ? orderCount?.pending - : card?.value === 4 - ? orderCount?.rejected - : card?.value === 5 - ? orderCount?.cancelled - : 0} + {count} - {/* {card.label} */} {card.icon} >; +}; + +export default function UpdateOrder({ + order_id, + open, + setOpen, +}: UpdateOrderProps) { + const [loading, setLoading] = useState(false); + const [order, setOrder] = useState(null); + const [influencer, setInfluencer] = useState(null); + + const getOrderDetails = async () => { + try { + setLoading(true); + const { isSuccess, data, message } = await getService( + `/orders/order/${order_id}/` + ); + if (isSuccess) { + setOrder(data?.data); + setInfluencer(data?.data?.order_item_order_id[0]?.package?.influencer); + } else { + notification(message ? message : "Something went wrong", "error"); + } + } finally { + setLoading(false); + } + }; + + const validateMetaDataValues = () => { + let isValid = true; + order?.order_item_order_id?.forEach((orderItem) => { + orderItem?.order_item_meta_data?.forEach((metaData) => { + if (metaData.regex && metaData?.value) { + const regex = new RegExp(metaData.regex); + if (!regex.test(metaData?.value)) { + notification( + `Please fill the correct value for ${metaData.label}`, + "error", + 3000 + ); + isValid = false; + } + } + }); + }); + return isValid; + }; + + const updateOrder = async () => { + const body = { + order_items: order?.order_item_order_id?.map((orderItem) => { + return { + order_item_id: orderItem?.id, + publish_date: orderItem?.publish_date, + meta_data: orderItem?.order_item_meta_data?.map((metaData) => { + return { + order_item_meta_data_id: metaData?.id, + service_master_meta_data_id: + metaData?.service_master_meta_data_id, + value: metaData?.value, + }; + }), + }; + }), + }; + try { + const { isSuccess, message } = await putService( + `/orders/order/${order_id}/`, + body + ); + if (isSuccess) { + notification("Order Details saved successfully!", "success"); + await getOrderDetails(); + } else { + notification(message, "error", 3000); + } + } finally { + } + }; + + const updateOrderItemMetaData = async ( + orderItemId: string, + orderItemMetaDataId: string, + value: string | null + ) => { + // The function will find the relevant order item and update the metadata in the order + + // First, create a copy of the order + const orderCopy = { ...order }; + + // Then find the order item + const orderItem = orderCopy?.order_item_order_id?.find( + (orderItem) => orderItem.id === orderItemId + ); + + // If order item not found, return + if (!orderItem) { + return; + } + + // Find the order item meta data + const orderItemMetaData = orderItem?.order_item_meta_data?.find( + (orderItemMetaData) => orderItemMetaData.id === orderItemMetaDataId + ); + + // If order item meta data not found, return + if (!orderItemMetaData) { + return; + } + + // Update the value + orderItemMetaData.value = value; + + // Update the order + setOrder(orderCopy); + }; + + const updatePublishDate = async ( + orderItemId: string, + publishDate: string + ) => { + // The function will find the relevant order item and update the metadata in the order + + // First, create a copy of the order + const orderCopy = { ...order }; + + // Then find the order item + const orderItem = orderCopy?.order_item_order_id?.find( + (orderItem) => orderItem.id === orderItemId + ); + + // If order item not found, return + if (!orderItem) { + return; + } + + // Update the publish date + orderItem.publish_date = publishDate; + + // Update the order + setOrder(orderCopy); + }; + + useEffect(() => { + if (order_id) { + getOrderDetails(); + } + }, [order_id]); + + return ( + + + + + + Edit Order Details: {order?.order_code} + + {order && ( + + + + Influencer + + + {influencer?.twitter_account?.user_name} + + + + + Total Amount + + {order?.amount + " " + order?.currency?.symbol} + + + + Status + + + + + + Order Date + + {dayjs(order?.created_at).format(DISPLAY_DATE_FORMAT)} + + + + + )} + {order?.order_item_order_id?.map( + (orderItem: any, index: number) => { + return ( + + ); + } + )} + + + + + + + + + + + ); +} diff --git a/src/ui/src/components/profileComponents/createUpdateService/index.tsx b/src/ui/src/components/profileComponents/createUpdateService/index.tsx index 17eada6d..8f7e74ba 100644 --- a/src/ui/src/components/profileComponents/createUpdateService/index.tsx +++ b/src/ui/src/components/profileComponents/createUpdateService/index.tsx @@ -227,7 +227,7 @@ const CreateUpdateService = ({ Select Service { if (typeof value === "object" && value) { diff --git a/src/ui/src/components/shared/statusChip/index.tsx b/src/ui/src/components/shared/statusChip/index.tsx index c1b15f8e..a2ca4d7e 100644 --- a/src/ui/src/components/shared/statusChip/index.tsx +++ b/src/ui/src/components/shared/statusChip/index.tsx @@ -13,6 +13,8 @@ const colorMap: any = { pending: "#F5F6C9", completed: "#CBF8D8", draft: "#F8F8F8", + published: "#CBF8D8", + scheduled: "#F5F6C9", }; const StatusChip: React.FC = ({ status }) => { diff --git a/src/ui/src/utils/types.ts b/src/ui/src/utils/types.ts index 0f098da8..71b34eaa 100644 --- a/src/ui/src/utils/types.ts +++ b/src/ui/src/utils/types.ts @@ -218,7 +218,7 @@ type OrderItemType = { created_at: Date | string; platform_fee: string; platform_price: string; - order_id?: string; + order_id?: OrderType; order_item_meta_data: OrderItemMetaDataType[]; publish_date?: string; published_tweet_id?: string;