Skip to content

Commit

Permalink
Feat: Delete products that are empty & create them
Browse files Browse the repository at this point in the history
  • Loading branch information
Gum-Joe committed Aug 21, 2024
1 parent a3dbde4 commit 0132bd2
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 5 deletions.
121 changes: 121 additions & 0 deletions collection/app/(app)/products/AddProduct.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client";

import { addProducts } from "@/lib/crud/products";
import { StatusReturn } from "@/lib/types";
import {
ActionIcon,
Alert,
Button,
Group,
InputLabel,
Modal,
NativeSelect,
Stack,
TextInput,
} from "@mantine/core";
import { useForm } from "@mantine/form";
import { useDisclosure } from "@mantine/hooks";
import React, { useState, useTransition } from "react";
import { FaPlus, FaTrash } from "react-icons/fa6";

interface AddProductProps {
academicYears: string[];
currentYear: string;
}

type AddProductFormProps = AddProductProps & {
close: () => void;
};

interface AddProductForm {
products: string[];
academicYear: string;
}

const AddProductForm: React.FC<AddProductFormProps> = ({ academicYears, currentYear, close }) => {
const [isPending, startTransition] = useTransition();
const [status, setStatus] = useState<StatusReturn>({
status: "pending",
});
const form = useForm<AddProductForm>({
mode: "controlled",
initialValues: {
products: [""],
academicYear: currentYear,
},
});

const submit = ({ academicYear, products }: { academicYear: string; products: string[] }) => {
startTransition(async () => {
const res = await addProducts(academicYear, products);

if (res.status === "error") {
setStatus(res);
} else {
close();
}
});
};

return (
<form onSubmit={form.onSubmit(submit)}>
<Stack>
{
// Display error if there is one
status.status === "error" && (
<Alert color="red" title="Error">
{status.error}
</Alert>
)
}
<NativeSelect
label="Academic year"
name="academicYear"
key={form.key("academicYear")}
description="Add products to this academic year"
data={academicYears}
required
{...form.getInputProps("academicYear")}
/>
<Group gap="sm">
<InputLabel>Products</InputLabel>
<ActionIcon onClick={() => form.insertListItem("products", "")}>
<FaPlus />
</ActionIcon>
</Group>
{form.getValues().products.map((product, i) => (
<Group key={form.key(`products.${i}`)}>
<TextInput
placeholder="E.g. Hoodie"
required
{...form.getInputProps(`products.${i}`)}
flex={"1 0 0"}
/>
<ActionIcon color="red" onClick={() => form.removeListItem("products", i)}>
<FaTrash />
</ActionIcon>
</Group>
))}
<Group justify="right" mt="sm">
<Button type="submit" color="green" loading={isPending}>
Add Products
</Button>
</Group>
</Stack>
</form>
);
};

export const AddProduct: React.FC<AddProductProps> = (props) => {
const [opened, { open, close }] = useDisclosure(false);
return (
<>
<Modal opened={opened} onClose={close} title="Add Products" size="xl">
<AddProductForm {...props} close={close} />
</Modal>
<Button leftSection={<FaPlus />} onClick={open}>
Add Products
</Button>
</>
);
};
27 changes: 27 additions & 0 deletions collection/app/(app)/products/DeleteProduct.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { deleteProduct } from "@/lib/crud/products";
import { Button } from "@mantine/core";
import React, { useTransition } from "react";
import { FaTrash } from "react-icons/fa6";

export const DeleteProduct = ({ productId }: { productId: number }) => {
const [isPending, startTransition] = useTransition();

const onClickHandler = () => {
startTransition(async () => {
await deleteProduct(productId);
});
};

return (
<Button
size="sm"
color="red"
leftSection={<FaTrash />}
variant="outline"
onClick={onClickHandler}
loading={isPending}
>
Delete Product
</Button>
);
};
14 changes: 11 additions & 3 deletions collection/app/(app)/products/VariantsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"use client";

import TanstackTable from "@/components/tables/TanStackTable";
import { Text } from "@mantine/core";
import { Group, Text } from "@mantine/core";
import { createColumnHelper } from "@tanstack/react-table";
import React from "react";

import { DeleteProduct } from "./DeleteProduct";

interface Variant {
id: number;
name: string;
Expand All @@ -30,11 +32,17 @@ const columns = [

interface VariantsTableProps {
variants: Variant[];
productId: number;
}

export const VariantsTable: React.FC<VariantsTableProps> = ({ variants }) => {
export const VariantsTable: React.FC<VariantsTableProps> = ({ variants, productId }) => {
if (!variants || variants.length === 0) {
return <Text>No variants added yet</Text>;
return (
<Group>
<Text>No variants added yet</Text>
<DeleteProduct productId={productId} />
</Group>
);
}

return (
Expand Down
1 change: 1 addition & 0 deletions collection/app/(app)/products/YearProducts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const YearProducts: React.FC<YearProductsProps> = ({ products }) => {
name: variant.variantName,
count: variant._count.OrderItem,
}))}
productId={product.id}
/>
</Stack>
</Grid.Col>
Expand Down
12 changes: 10 additions & 2 deletions collection/app/(app)/products/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import TanstackTable from "@/components/tables/TanStackTable";
import { getAcademicYear } from "@/lib/config";
import { getAcademicYearsInDB } from "@/lib/crud/academic-year";
import {
getProductsAndVariantByAcademicYearWithCounts,
ProductsAndVariantsByAcademicYear,
} from "@/lib/crud/products";
import { Alert, Grid, GridCol, Stack, Title } from "@mantine/core";
import { Alert, Grid, GridCol, Group, Stack, Title } from "@mantine/core";
import { createColumnHelper } from "@tanstack/react-table";
import React from "react";
import { FaInfoCircle } from "react-icons/fa";

import { AddProduct } from "./AddProduct";
import { VariantsTable } from "./VariantsTable";
import { YearProducts } from "./YearProducts";

Expand Down Expand Up @@ -36,9 +39,14 @@ const columns = [

export const ProductsPage = async () => {
const data = await getProductsAndVariantByAcademicYearWithCounts();
const academicYears = await getAcademicYearsInDB();
const currentYear = await getAcademicYear();
return (
<Stack gap="xl">
<Title order={1}>Products by Academic Year</Title>
<Group justify="space-between">
<Title order={1}>Products by Academic Year</Title>
<AddProduct academicYears={academicYears} currentYear={currentYear} />
</Group>
<Alert color="blue" title="Note" icon={<FaInfoCircle />}>
Variants will be added automatically on import
</Alert>
Expand Down
86 changes: 86 additions & 0 deletions collection/lib/crud/products.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"use server";

import { isValidAcademicYear } from "@docsoc/eactivities";
import { RootItem } from "@prisma/client";
import { revalidatePath } from "next/cache";

import prisma from "../db";
import { StatusReturn } from "../types";

export const getProductsByAcademicYear = async (): Promise<Record<string, RootItem[]>> => {
// group by res.academic year, just need name and id
Expand Down Expand Up @@ -69,3 +72,86 @@ export interface ProductsAndVariantsByAcademicYear {
}[];
}[];
}

export async function addProducts(academicYear: string, products: string[]): Promise<StatusReturn> {
if (products.length === 0) {
return {
status: "error",
error: "No products provided",
};
}

if (!isValidAcademicYear(academicYear)) {
return {
status: "error",
error: "Invalid academic year",
};
}

try {
await prisma.rootItem.createMany({
data: products
.map((product) => product.trim())
.filter((product) => product !== "")
.map((product) => ({
academicYear,
name: product,
})),
});
} catch (e: any) {
if (e?.code === "P2002" && e?.meta?.target?.includes("name")) {
return {
status: "error",
error: "Product already exists",
};
} else {
return {
status: "error",
error: e.message ?? e.toString(),
};
}
}

revalidatePath("/products");
revalidatePath("/");

return {
status: "success",
};
}

export async function deleteProduct(productId: number): Promise<StatusReturn> {
try {
// Validate it has no variants
const variants = await prisma.variant.findMany({
where: {
rootItemId: productId,
},
});

if (variants.length > 0) {
return {
status: "error",
error: "Product has variants",
};
}

await prisma.rootItem.delete({
where: {
id: productId,
},
});
} catch (e: any) {
return {
status: "error",
error: e.message ?? e.toString(),
};
}

revalidatePath("/products");
revalidatePath("/");

return {
status: "success",
};
}

0 comments on commit 0132bd2

Please sign in to comment.