Skip to content

Commit

Permalink
feat: Product selection UI for sync
Browse files Browse the repository at this point in the history
  • Loading branch information
Gum-Joe committed Oct 19, 2024
1 parent 00bb9fa commit 630aedc
Show file tree
Hide file tree
Showing 8 changed files with 307 additions and 78 deletions.
142 changes: 122 additions & 20 deletions collection/app/(app)/SyncEActivities.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,137 @@
"use client";

import { loadSalesFromEActivites } from "@/lib/crud/loadSalesFromEActivites";
import { Button } from "@mantine/core";
import React, { useTransition } from "react";
import { fetcher } from "@/lib/fetcher";
import { StatusReturn } from "@/lib/types";
import { Product } from "@docsoc/eactivities";
import { Alert, Box, Button, Checkbox, Group, Loader, Modal, Stack, Text } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { RootItem } from "@prisma/client";
import React, { useCallback, useState, useTransition } from "react";
import { FaSync } from "react-icons/fa";
import { FaUpRightFromSquare } from "react-icons/fa6";
import useSWR from "swr";

function CheckboxesForProducts({ close }: { close: () => void }) {
const [value, setValue] = useState<string[]>([]);
const [status, setStatus] = useState<StatusReturn>({
status: "pending",
});

const { data: products, isLoading } = useSWR<RootItem[]>("/api/products/syncable", fetcher);

export const SyncEActivities = ({
setActionsError,
}: {
setActionsError: (error: string | null) => void;
}) => {
const [isPending, startTransition] = useTransition();

const syncEActivities = () => {
setActionsError(null);
const syncEActivities = useCallback(() => {
setStatus({
status: "pending",
});
startTransition(async () => {
const res = await loadSalesFromEActivites();
const res = await loadSalesFromEActivites(
value.map((id) => parseInt(id, 10)).filter(isFinite),
);
if (res.status === "error") {
setActionsError(res.error);
setStatus(res);
} else {
setStatus({
status: "success",
});
close();
}
});
};
}, [close, value]);

const cards = products?.map((product, i) => (
<Checkbox.Card radius="md" value={product.id.toString(10)} key={i} flex="1 0 0">
<Group wrap="nowrap" align="center" p="md">
<Checkbox.Indicator />
<Group flex="1 0 0">
<Box flex="1 0 0">
<Text>
<b>
<u>{product.name}</u>
</b>
</Text>
<Text>
<b>eActivites ID:</b> {product.eActivitiesId}
</Text>
<Text>
<b>eActivites Name:</b> {product.eActivitiesName}
</Text>
</Box>
{isLoading ? (
<Loader size="md" />
) : (
product.eActivitiesURL && (
<Button
component="a"
href={product.eActivitiesURL}
target="_blank"
rightSection={<FaUpRightFromSquare />}
>
View on Union Shop
</Button>
)
)}
</Group>
</Group>
</Checkbox.Card>
));

return (
<Stack>
{
// Display error if there is one
status.status === "error" && (
<Alert color="red" title="Error">
{status.error}
</Alert>
)
}
<Checkbox.Group
value={value}
onChange={setValue}
label="Select products to sync"
description="If you are being IP banned, you might want to wait a few minutes and try fewer products"
>
<Stack pt="md" gap="xs">
{products && products.length > 0 ? cards : <Text>No products found</Text>}
</Stack>
</Checkbox.Group>

<Group justify="space-between" mt="sm">
<Button
onClick={() =>
setValue(products?.map((product) => product.id.toString(10)) ?? [])
}
color="violet"
loading={false}
>
Select all
</Button>
<Button onClick={syncEActivities} color="green" loading={isPending}>
Sync {value.length} products
</Button>
</Group>
</Stack>
);
}

export const SyncEActivities = ({
setActionsError,
}: {
setActionsError: (error: string | null) => void;
}) => {
const [opened, { open, close }] = useDisclosure(false);

return (
<Button
leftSection={<FaSync />}
color="violet"
loading={isPending}
onClick={syncEActivities}
>
Sync from eActivities
</Button>
<>
<Modal opened={opened} onClose={close} title={`Select products to sync`} size="xl">
<CheckboxesForProducts close={close} />
</Modal>
<Button leftSection={<FaSync />} color="violet" onClick={open}>
Sync from eActivities
</Button>
</>
);
};
45 changes: 8 additions & 37 deletions collection/app/(app)/products/MapProduct.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
"use client";

import { ProductSelectionCard } from "@/components/ProductSelectionCard";
import {
addProducts,
ProductsAndVariantsByAcademicYear,
updateProductWithEActivitesMetadata,
} from "@/lib/crud/products";
import { fetcher } from "@/lib/fetcher";
import { StatusReturn } from "@/lib/types";
import { Product } from "@docsoc/eactivities";
import {
ActionIcon,
Alert,
Button,
Group,
InputLabel,
Modal,
NativeSelect,
Radio,
Stack,
TextInput,
Tooltip,
Text,
Anchor,
Box,
Loader,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { useDisclosure } from "@mantine/hooks";
import React, { useEffect, useState, useTransition } from "react";
import { FaEdit } from "react-icons/fa";
import { FaPlus, FaSignsPost, FaTrash, FaUpRightFromSquare } from "react-icons/fa6";
import useSWR from "swr";
import { FaSignsPost, FaUpRightFromSquare } from "react-icons/fa6";

interface MapProductProps {
academicYears: string[];
Expand Down Expand Up @@ -111,35 +105,12 @@ const MapProductForm: React.FC<MapProductFormProps> = ({
}, [academicYear]);

const cards = products.map((product) => (
<Radio.Card radius="md" key={product.ID} value={product.ID.toString(10)} flex="1 0 0">
<Group wrap="nowrap" align="center" p="md">
<Radio.Indicator />
<Group flex="1 0 0">
<Box flex="1 0 0">
<Text>
<b>
<u>{product.Name}</u>
</b>
</Text>
<Text>
<b>ID:</b> {product.ID}
</Text>
<Text>
<b>Variants:</b>{" "}
{product.ProductLines?.map((line) => line.Name).join(" | ")}
</Text>
</Box>
<Button
component="a"
href={product.URL}
target="_blank"
rightSection={<FaUpRightFromSquare />}
>
View on Union Shop
</Button>
</Group>
</Group>
</Radio.Card>
<ProductSelectionCard
product={product}
key={product.ID}
cardParent={Radio.Card}
indicator={Radio.Indicator}
/>
));

return (
Expand Down
20 changes: 20 additions & 0 deletions collection/app/api/products/syncable/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Returns just those products we can sync from eActivities
*/
import { auth } from "@/auth";
import { getSyncableProducts } from "@/lib/crud/products";
import { NextResponse } from "next/server";

export const GET = auth(async function GET(request) {
if (!request.auth) {
return NextResponse.json(
{
message: "Unauthorized",
},
{ status: 401 },
);
}
return NextResponse.json(await getSyncableProducts());
});

export const dynamic = "force-dynamic";
48 changes: 48 additions & 0 deletions collection/components/ProductSelectionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Product } from "@docsoc/eactivities";
import { Checkbox, Group, Radio, Text, Box, Button } from "@mantine/core";
import React from "react";
import { FaUpRightFromSquare } from "react-icons/fa6";

interface ProductSelectionCardProps {
cardParent: typeof Radio.Card | typeof Checkbox.Card;
indicator: typeof Radio.Indicator | typeof Checkbox.Indicator;
product: Product;
}

export const ProductSelectionCard: React.FC<ProductSelectionCardProps> = ({
cardParent: CardParent,
indicator: Indicator,
product,
}) => {
return (
<CardParent radius="md" key={product.ID} value={product.ID.toString(10)} flex="1 0 0">
<Group wrap="nowrap" align="center" p="md">
<Indicator />
<Group flex="1 0 0">
<Box flex="1 0 0">
<Text>
<b>
<u>{product.Name}</u>
</b>
</Text>
<Text>
<b>ID:</b> {product.ID}
</Text>
<Text>
<b>Variants:</b>{" "}
{product.ProductLines?.map((line) => line.Name).join(" | ")}
</Text>
</Box>
<Button
component="a"
href={product.URL}
target="_blank"
rightSection={<FaUpRightFromSquare />}
>
View on Union Shop
</Button>
</Group>
</Group>
</CardParent>
);
};
Loading

0 comments on commit 630aedc

Please sign in to comment.