Skip to content

Commit

Permalink
Pages order info (#2104)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikaas24 authored Nov 7, 2024
2 parents a0974e2 + 503ba00 commit a48df64
Show file tree
Hide file tree
Showing 14 changed files with 301 additions and 60 deletions.
17 changes: 15 additions & 2 deletions backend/apps/ecommerce/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,22 @@ def resolve_user_orders(self, info):
def resolve_all_user_orders(self, info):
return Order.objects.all()

@login_required
@staff_member_required
def resolve_all_shop_orders(self, info):
return Order.objects.filter(product__shop_item=True)
def resolve_paginated_shop_orders(self, info, limit, offset):
# Apply pagination if limit and offset are provided
orders = Order.objects.filter(product__shop_item=True).order_by(
"delivered_product", "payment_status", "timestamp"
)
if offset is None:
offset = 0
orders = orders[offset:]

if limit is None or limit > 300:
# Add hard cap of maximum 300 orders per query. A bigger query would crash the website
limit = 300
orders = orders[:limit]
return orders

@staff_member_required
def resolve_orders_by_status(self, info: "ResolveInfo", product_id, status):
Expand Down
2 changes: 1 addition & 1 deletion backend/apps/ecommerce/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class EcommerceQueries(graphene.ObjectType, EcommerceResolvers):
order = graphene.Field(OrderType, order_id=graphene.ID(required=True))
user_orders = graphene.List(NonNull(OrderType))
all_user_orders = graphene.List(NonNull(OrderType))
all_shop_orders = graphene.List(NonNull(OrderType))
paginated_shop_orders = graphene.List(graphene.NonNull(OrderType), limit=graphene.Int(), offset=graphene.Int())

orders_by_status = graphene.Field(
OrdersByStatusType, product_id=graphene.ID(required=True), status=graphene.String(required=True)
Expand Down
82 changes: 82 additions & 0 deletions backend/apps/ecommerce/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,3 +457,85 @@ def test_delivered_product(self) -> None:

order = Order.objects.get(pk=order.id)
self.assertTrue(order.delivered_product)


class PaginatedShopOrdersResolverTests(ExtendedGraphQLTestCase):
def setUp(self):
# Create a staff user using the StaffUserFactory
self.staff_user = StaffUserFactory(username="staffuser")

# Ensure the user satisfies the requirements for the new decorators
self.staff_user.is_staff = True # Required for @staff_member_required
self.staff_user.is_superuser = True # Required for @superuser_required if added
self.staff_user.save()

# Create a product with `shop_item=True` to pass the resolver's filter condition
self.product = ProductFactory(
name="Test Product",
price=decimal.Decimal("1000.00"),
description="A test product description",
max_buyable_quantity=2,
total_quantity=5,
shop_item=True, # Ensure this matches the resolver's filter condition
)

# Create multiple orders associated with the product and user
self.orders = [
OrderFactory(
product=self.product,
user=self.staff_user,
payment_status=Order.PaymentStatus.INITIATED,
)
for i in range(10)
]

def test_paginated_shop_orders_with_fragment_and_product(self):
query = """
query paginatedShopOrders($limit: Int, $offset: Int) {
paginatedShopOrders(limit: $limit, offset: $offset) {
...Order
}
}
fragment Order on OrderType {
id
quantity
totalPrice
paymentStatus
timestamp
deliveredProduct
product {
...Product
}
}
fragment Product on ProductType {
id
name
price
description
maxBuyableQuantity
shopItem
}
"""

# Execute the query using the query method from ExtendedGraphQLTestCase
response = self.query(query, variables={"limit": 5, "offset": 2}, user=self.staff_user) # Use staff user
data = json.loads(response.content)

# Check if the response data matches expectations
self.assertIn("data", data)
self.assertIn("paginatedShopOrders", data["data"])
self.assertEqual(len(data["data"]["paginatedShopOrders"]), 5)

# Verify the structure of the nested product field in the first order
first_order = data["data"]["paginatedShopOrders"][0]
self.assertIn("product", first_order)
self.assertEqual(first_order["product"]["name"], "Test Product")
self.assertEqual(first_order["product"]["price"], "1000.00") # Adjusted for consistency
self.assertTrue(first_order["product"]["shopItem"])

# Additional checks for other fields
self.assertIn("quantity", first_order)
self.assertIn("paymentStatus", first_order)
self.assertIn("timestamp", first_order)
25 changes: 23 additions & 2 deletions backend/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -301,11 +301,32 @@
}
},
{
"args": [],
"args": [
{
"defaultValue": null,
"description": null,
"name": "limit",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
},
{
"defaultValue": null,
"description": null,
"name": "offset",
"type": {
"kind": "SCALAR",
"name": "Int",
"ofType": null
}
}
],
"deprecationReason": null,
"description": null,
"isDeprecated": false,
"name": "allShopOrders",
"name": "paginatedShopOrders",
"type": {
"kind": "LIST",
"name": null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { Swiper, SwiperSlide } from "swiper/react";
// Import Swiper styles
import "swiper/css";

import { Organization, OrganizationLink } from "./OrganizationLink";

import { Link } from "@/app/components/Link";

import { Organization, OrganizationLink } from "./OrganizationLink";

const organizations: Readonly<Organization[]> = [
{ name: "Janus Sosial", internalUrl: "/janus" },
{ name: "Bindeleddet", externalUrl: "https://www.bindeleddet.no" },
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/app/_components/LandingHero/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import { Box, Button, Container, Unstable_Grid2 as Grid, Typography } from "@mui/material";
import Image from "next/image";

import { OrganizationsSlider } from "./OrganizationsSlider";

import { Link } from "@/app/components/Link";
import Hero from "~/public/static/landing/hero.webp";

import { OrganizationsSlider } from "./OrganizationsSlider";

export const LandingHero: React.FC = () => {
return (
<>
Expand Down
93 changes: 66 additions & 27 deletions frontend/src/components/pages/organization/OrgShop.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,58 @@
import { useQuery } from "@apollo/client";
import { Box, Grid, Stack, Typography } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import { useState } from "react";

import { AdminOrganizationFragment, AllShopOrdersDocument } from "@/generated/graphql";
import { AdminOrganizationFragment, PaginatedShopOrdersDocument } from "@/generated/graphql";

import { ShopSale } from "../orgs/ShopSale";
import { TableStepper } from "../orgs/TableStepper";

type Props = {
organization: AdminOrganizationFragment;
};
export const OrgProducts: React.FC<Props> = ({ organization }) => {
const { data, error } = useQuery(AllShopOrdersDocument);
const theme = useTheme();
const limit = 5;
const [page, setPage] = useState(0);

const handlePageChange = (newValue: number) => {
setPage(newValue);
};

const { data, error, loading } = useQuery(PaginatedShopOrdersDocument, {
variables: {
limit: limit + 1, // The number of orders you want to fetch
offset: page * limit, // The starting index (e.g., 0 for the first set of results)
},
});
if (error) return <p>Error</p>;

console.log(data);
if (organization.name !== "Janus linjeforening") {
if (organization.name.toLowerCase() !== "janus linjeforening") {
return (
<p>
Per nå har kun Janus tilgang på buttikk administrasjon. Etter hvert vil vi åpne for at flere kan bruke siden
</p>
);
}
if (data?.allShopOrders?.length === 0) {
if (data?.paginatedShopOrders!.length === 0) {
return <p>Ingen ordre</p>;
}
// Check if there is a next page (if we received less than `limit` items)
const hasNextPage = (data?.paginatedShopOrders && data.paginatedShopOrders.length < limit) ?? false;

return (
<>
<Stack direction={"row"} padding={2} border={3}>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
<TableStepper page={page} hasNextPage={hasNextPage} handlePageChange={handlePageChange} />
<Stack
direction={"row"}
marginTop={"36px"}
spacing={0}
padding={1}
borderBottom={1}
sx={{ bgcolor: theme.palette.background.elevated, color: theme.palette.text.primary }}
>
<Box display="flex" alignItems="left" justifyContent="left" width={"25%"} padding={1}>
<Typography variant="body1">Navn på kunde</Typography>
</Box>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
Expand All @@ -36,33 +62,46 @@ export const OrgProducts: React.FC<Props> = ({ organization }) => {
<Typography variant="body1">Antall bestilt</Typography>
</Box>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
<Typography variant="body1"> Betalt status</Typography>
<Typography variant="body1">Betalt status</Typography>
</Box>
<Box display="flex" alignItems="left" justifyContent="left" width={"25%"} padding={1}>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
Mulige handlinger
</Box>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
<Typography variant="body1"> Har vi levert varen</Typography>
<Typography variant="body1">Har vi levert varen</Typography>
</Box>
</Stack>
<Grid container spacing={0}>
{data?.allShopOrders?.map((order) => {
return (
<Grid key={order.id} item xs={12} sm={12} md={12}>
<Stack border={1}>
<ShopSale
name={order.user.firstName + " " + order.user.lastName}
product_name={order.product.name}
quantity={order.quantity}
has_paid={order.paymentStatus === "CAPTURED"}
is_delivered={order.deliveredProduct === true}
order_id={order.id}
/>
</Stack>

{loading ? (
<Typography padding={1}>Loading...</Typography>
) : (
data && (
<>
<Grid container spacing={0}>
{data?.paginatedShopOrders?.slice(0, limit).map((order) => {
return (
<Grid key={order.id} item xs={12} sm={12} md={12}>
<Stack borderBottom={1} borderColor={"gray"}>
<ShopSale
name={order.user.firstName + " " + order.user.lastName}
product_name={order.product.name}
quantity={order.quantity}
has_paid={order.paymentStatus === "CAPTURED"}
is_delivered={order.deliveredProduct === true}
order_id={order.id}
/>
</Stack>
</Grid>
);
})}
</Grid>
);
})}
</Grid>

<Stack marginTop={"18px"}>
<TableStepper page={page} hasNextPage={hasNextPage} handlePageChange={handlePageChange} />
</Stack>
</>
)
)}
</>
);
};
8 changes: 4 additions & 4 deletions frontend/src/components/pages/orgs/ShopSale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ export const ShopSale: React.FC<Props> = ({ name, product_name, quantity, has_pa
deliverProduct({ variables: { orderId: order_id } });
}
return (
<Stack direction={"row"} padding={2} spacing={1}>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
<Stack direction={"row"} padding={1} spacing={0}>
<Box display="flex" alignItems="left" justifyContent="left" width={"25%"} padding={1}>
<Typography variant="body1">{name}</Typography>
</Box>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
Expand All @@ -36,7 +36,7 @@ export const ShopSale: React.FC<Props> = ({ name, product_name, quantity, has_pa
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
<Typography variant="body1">Betalt: {has_paid ? "Ja" : "Nei"}</Typography>
</Box>
<Box display="flex" alignItems="left" justifyContent="left" width={"25%"} padding={1}>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
<Stack direction={"row"} spacing={1}>
<Tooltip title="Levert">
<Box display="inline" component="span">
Expand Down Expand Up @@ -69,7 +69,7 @@ export const ShopSale: React.FC<Props> = ({ name, product_name, quantity, has_pa
</Stack>
</Box>
<Box display="flex" alignItems="left" justifyContent="left" width={"15%"} padding={1}>
<Typography variant="body1">Varen er {delivered ? "levert" : "ikke levert"}</Typography>
<Typography variant="body1">{delivered ? "Levert" : "Ikke levert"}</Typography>
</Box>
</Stack>
);
Expand Down
41 changes: 41 additions & 0 deletions frontend/src/components/pages/orgs/TableStepper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { KeyboardArrowLeft, KeyboardArrowRight, KeyboardDoubleArrowLeft, KeyboardDoubleArrowRight } from "@mui/icons-material";
import { Button, Typography, Stack } from "@mui/material";
import { useTheme } from "@mui/material/styles";
import React from "react";

type Props = {
hasNextPage: boolean;
page: number;
handlePageChange: (page: number) => void;
};

export const TableStepper: React.FC<Props> = ({ hasNextPage, page, handlePageChange }) => {
const theme = useTheme();
const nextPage = () => handlePageChange(page + 1);
const prevPage = () => handlePageChange(page > 0 ? page - 1 : 0);
const resetPage = () => handlePageChange(0)

return (
<Stack
sx={{ maxWidth: 300, flexGrow: 1, position: "static", flexDirection: "row", justifyContent: "space-between" }}
>
<Stack sx ={{flexDirection: "row"}}>
<Button size="small" onClick={resetPage} disabled={page === 0}>
{theme.direction === "rtl" ? <KeyboardDoubleArrowRight /> : <KeyboardDoubleArrowLeft />}
First
</Button>
<Button size="small" onClick={prevPage} disabled={page === 0}>
{theme.direction === "rtl" ? <KeyboardArrowRight /> : <KeyboardArrowLeft />}
Back
</Button>
</Stack>

<Typography>{page + 1}</Typography>

<Button size="small" onClick={nextPage} disabled={hasNextPage}>
Next
{theme.direction === "rtl" ? <KeyboardArrowLeft /> : <KeyboardArrowRight />}
</Button>
</Stack>
);
};
Loading

0 comments on commit a48df64

Please sign in to comment.