Skip to content

Commit

Permalink
feat: add filter transaction by category
Browse files Browse the repository at this point in the history
  • Loading branch information
ainunns committed Dec 1, 2024
1 parent 67b74a3 commit 7509d00
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 19 deletions.
7 changes: 5 additions & 2 deletions resources/js/Components/Badge.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import * as React from "react";
import Typography from "./Typography";

export default function Badge({ children }: { children: React.ReactNode }) {
export default function Badge({
children,
className,
}: { children: React.ReactNode; className?: string }) {
return (
<div className="py-1 px-3 bg-secondary-500 border-primary-500 border rounded-lg">
<Typography variant="s3" className="">
<Typography variant="s3" className={className}>
{children}
</Typography>
</div>
Expand Down
33 changes: 27 additions & 6 deletions resources/js/Pages/Product/Components/PopupFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,43 @@ export type PopupFilterProps<T extends Record<string, string[]>> = {
name: string;
}[];
}[];
filterQuery: T;
setFilterQuery: React.Dispatch<React.SetStateAction<T>>;
onResetFilter?: () => void;
title?: string;
} & React.ComponentPropsWithoutRef<"div">;

type FormData = {
filter: string[];
};

export default function PopupFilter<T extends Record<string, string[]>>({
filterOption,
filterQuery,
setFilterQuery,
onResetFilter,
title = "Filter",
}: PopupFilterProps<T>) {
//#region //*=========== Form ===========
const methods = useForm({
const defaultFilterValues = React.useMemo(() => {
return Object.entries(filterQuery).reduce((acc, [key, values]) => {
return [...acc, ...values.map((value) => `${key}.${value}`)];
}, [] as string[]);
}, [filterQuery]);

const methods = useForm<FormData>({
mode: "onTouched",
defaultValues: {
filter: defaultFilterValues,
},
});
const { control, setValue } = methods;

const filter: string[] = useWatch({
control,
name: "filter[]",
});
const filter =
useWatch({
control,
name: "filter",
}) ?? [];
//#endregion //*======== Form ===========

React.useEffect(() => {
Expand All @@ -56,7 +74,10 @@ export default function PopupFilter<T extends Record<string, string[]>>({
setFilterQuery(parsedFilter);
}, [filter, filterOption, setFilterQuery]);

const resetFilter = () => setValue("filter[]", []);
const resetFilter = () => {
onResetFilter?.();
setValue("filter", []);
};

return (
<Popover>
Expand Down
1 change: 1 addition & 0 deletions resources/js/Pages/Product/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ const ProductIndex = ({
</div>
<div className="flex gap-2">
<PopupFilter
filterQuery={filterQuery}
filterOption={filterOption}
setFilterQuery={setFilterQuery}
/>
Expand Down
101 changes: 90 additions & 11 deletions resources/js/Pages/Transaction/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,95 @@ import {
AccordionItem,
AccordionTrigger,
} from "@/Components/Accordion";
import Badge from "@/Components/Badge";
import Typography from "@/Components/Typography";
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout";
import { cn, numberToCurrency } from "@/Lib/utils";
import { CartType } from "@/types/entities/cart";
import { CategoryType } from "@/types/entities/category";
import { TransactionType } from "@/types/entities/transaction";
import { Head } from "@inertiajs/react";
import { router } from "@inertiajs/react";
import { format } from "date-fns";
import { Search, XCircle } from "lucide-react";
import * as React from "react";
import PopupFilter, {
PopupFilterProps,
} from "../Product/Components/PopupFilter";
import NotFound from "./container/NotFound";

type CategoryFilter = {
category: string[];
};

type TransactionIndexProps = {
transaction: TransactionType[];
category: CategoryType[];
categoryIds: string[];
};

export default function TransactionIndex({
transaction,
}: { transaction: TransactionType[] }) {
category,
categoryIds,
}: TransactionIndexProps) {
const [filter, setFilter] = React.useState<string>("");

const filteredTransaction = transaction.filter((t) => {
const matchesText = t.cart_product.some((cp) =>
cp.product.name.toLowerCase().includes(filter.toLowerCase()),
);
return matchesText;
const [filterQuery, setFilterQuery] = React.useState<CategoryFilter>({
category: categoryIds,
});

const prevCategory = React.useRef<string[]>(categoryIds);

const filterOption: PopupFilterProps<CategoryFilter>["filterOption"] =
React.useMemo(
() => [
{
id: "category",
name: "Category",
options: category,
},
],
[category],
);

const filteredTransaction = React.useMemo(() => {
return transaction.filter((t) => {
const matchesText = t.cart_product.some((cp) =>
cp.product.name.toLowerCase().includes(filter.toLowerCase()),
);
return matchesText;
});
}, [transaction, filter]);

const handleFilterChange = React.useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setFilter(String(e.target.value));
},
[],
);

const handleClearFilter = React.useCallback(() => {
setFilter("");
}, []);

React.useEffect(() => {
if (
JSON.stringify(filterQuery.category) !==
JSON.stringify(prevCategory.current)
) {
router.get(
"/history",
{ category_id: filterQuery.category },
{
preserveState: true,
preserveScroll: true,
},
);
prevCategory.current = filterQuery.category;
}
}, [filterQuery.category]);

return (
<AuthenticatedLayout>
<Head title="Order List" />
Expand All @@ -46,9 +112,7 @@ export default function TransactionIndex({
<input
type="text"
value={filter ?? ""}
onChange={(e) => {
setFilter(String(e.target.value));
}}
onChange={handleFilterChange}
className={cn(
"flex w-full rounded-lg shadow-sm",
"min-h-[2.25rem] py-0 px-10 md:min-h-[2.5rem]",
Expand All @@ -60,14 +124,22 @@ export default function TransactionIndex({
<div className="absolute inset-y-0 right-0 flex items-center pr-2">
<button
type="button"
onClick={() => setFilter("")}
onClick={handleClearFilter}
className="p-1"
>
<XCircle className="text-xl text-typo-icons" />
</button>
</div>
)}
</div>
<div className="flex gap-2">
<PopupFilter
filterQuery={filterQuery}
filterOption={filterOption}
setFilterQuery={setFilterQuery}
onResetFilter={() => setFilterQuery({ category: [] })}
/>
</div>
</div>
<div className="flex flex-col w-full gap-4">
{filteredTransaction.map((t) => (
Expand Down Expand Up @@ -166,10 +238,17 @@ function ProductItems({ cart_product: cp, quantity }: ProductItemsProps) {
alt={cp.product.name}
className="size-20 object-fit rounded"
/>
<div className="flex flex-col gap-1 w-full">
<div className="flex flex-col gap-2 w-full">
<Typography variant="s2" className="text-typo">
{cp.product.name}
</Typography>
<div className="flex gap-2">
{cp.product.category.map((c) => (
<Badge key={c.id} className="text-xs">
{c.name}
</Badge>
))}
</div>
<Typography variant="s3" className="text-typo-secondary">
{quantity} item{quantity > 1 && "s"} x{" "}
{numberToCurrency(cp.product.price)}
Expand Down

0 comments on commit 7509d00

Please sign in to comment.