From e9e3c8c9f8cc22af315ac67dbc663ab88beb24ae Mon Sep 17 00:00:00 2001 From: Derian Date: Sat, 23 Nov 2024 14:01:22 -0600 Subject: [PATCH] refactor: extract shared order filtering logic into custom hook --- apps/web/src/app/user/my-orders/page.tsx | 100 ++++---------- apps/web/src/app/user/my-sales/page.tsx | 129 +++++------------- apps/web/src/hooks/user/useOrderFiltering.tsx | 112 +++++++++++++++ apps/web/src/types/user/order.ts | 30 ++++ 4 files changed, 199 insertions(+), 172 deletions(-) create mode 100644 apps/web/src/hooks/user/useOrderFiltering.tsx create mode 100644 apps/web/src/types/user/order.ts diff --git a/apps/web/src/app/user/my-orders/page.tsx b/apps/web/src/app/user/my-orders/page.tsx index 5ede822..538068f 100644 --- a/apps/web/src/app/user/my-orders/page.tsx +++ b/apps/web/src/app/user/my-orders/page.tsx @@ -5,21 +5,13 @@ import { zodResolver } from "@hookform/resolvers/zod"; import Button from "@repo/ui/button"; import CheckBox from "@repo/ui/form/checkBox"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import OrderListItem from "~/app/_components/features/OrderListItem"; import { ProfileOptionLayout } from "~/app/_components/features/ProfileOptionLayout"; import BottomModal from "~/app/_components/ui/BottomModal"; - -const SalesStatusEnum = { - Paid: "Paid", - Prepared: "Prepared", - Shipped: "Shipped", - Delivered: "Delivered", -} as const; - -type SalesStatus = (typeof SalesStatusEnum)[keyof typeof SalesStatusEnum]; +import { useOrderFiltering } from "~/hooks/user/useOrderFiltering"; +import { type SalesStatus, SalesStatusEnum } from "~/types/user/order"; const filtersSchema = z.object({ statusPaid: z.boolean().optional(), @@ -67,80 +59,38 @@ const mockedOrders = [ }, ]; +const filtersDefaults = { + statusPaid: false, + statusPrepared: false, + statusShipped: false, + statusDelivered: false, +}; + export default function MyOrders() { - const [searchTerm, setSearchTerm] = useState(""); - const [isFiltersModalOpen, setIsFiltersModalOpen] = useState(false); - const [filteredOrders, setFilteredOrders] = useState(mockedOrders); - const [activeFilters, setActiveFilters] = useState({}); + const { + searchTerm, + setSearchTerm, + isFiltersModalOpen, + openFiltersModal, + closeFiltersModal, + filteredOrders, + applyFilters, + } = useOrderFiltering({ + orders: mockedOrders, + searchKey: "sellerName", + filters: filtersDefaults, + }); const router = useRouter(); - const openFiltersModal = () => { - setIsFiltersModalOpen(true); - }; - - const closeFiltersModal = () => { - setIsFiltersModalOpen(false); - }; - const { control, handleSubmit } = useForm({ resolver: zodResolver(filtersSchema), - defaultValues: { - statusPaid: false, - statusPrepared: false, - statusShipped: false, - statusDelivered: false, - }, + defaultValues: filtersDefaults, }); - const onSubmit = (data: FormValues) => { - console.log(data); - setActiveFilters(data); - closeFiltersModal(); - }; - const handleItemClick = (id: string) => { router.push(`/user/my-orders/${id}`); }; - useEffect(() => { - const newFilteredOrders = mockedOrders - .map((orderGroup) => ({ - ...orderGroup, - items: orderGroup.items.filter((item) => { - const matchesSearch = searchTerm - ? item.sellerName.toLowerCase().includes(searchTerm.toLowerCase()) - : true; - - const activeStatusFilters = [ - activeFilters.statusPaid, - activeFilters.statusPrepared, - activeFilters.statusShipped, - activeFilters.statusDelivered, - ]; - - const matchesStatus = activeStatusFilters.some(Boolean) - ? (activeFilters.statusPaid && - item.status === SalesStatusEnum.Paid) ?? - (activeFilters.statusPrepared && - item.status === SalesStatusEnum.Prepared) ?? - (activeFilters.statusShipped && - item.status === SalesStatusEnum.Shipped) ?? - (activeFilters.statusDelivered && - item.status === SalesStatusEnum.Delivered) - : true; - - return matchesSearch && matchesStatus; - }), - })) - .filter((orderGroup) => orderGroup.items.length > 0); - - if (!searchTerm && !Object.values(activeFilters).some(Boolean)) { - setFilteredOrders(mockedOrders); - } else { - setFilteredOrders(newFilteredOrders); - } - }, [searchTerm, activeFilters]); - return (
@@ -175,7 +125,7 @@ export default function MyOrders() { handleItemClick(order.id)} /> @@ -190,7 +140,7 @@ export default function MyOrders() {
-
+

Status

diff --git a/apps/web/src/app/user/my-sales/page.tsx b/apps/web/src/app/user/my-sales/page.tsx index b6bece5..6d5f07f 100644 --- a/apps/web/src/app/user/my-sales/page.tsx +++ b/apps/web/src/app/user/my-sales/page.tsx @@ -5,29 +5,18 @@ import { zodResolver } from "@hookform/resolvers/zod"; import Button from "@repo/ui/button"; import CheckBox from "@repo/ui/form/checkBox"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import OrderListItem from "~/app/_components/features/OrderListItem"; import { ProfileOptionLayout } from "~/app/_components/features/ProfileOptionLayout"; import BottomModal from "~/app/_components/ui/BottomModal"; - -const SalesStatusEnum = { - Paid: "Paid", - Prepared: "Prepared", - Shipped: "Shipped", - Delivered: "Delivered", -} as const; - -type SalesStatus = (typeof SalesStatusEnum)[keyof typeof SalesStatusEnum]; - -const DeliveryMethodEnum = { - Address: "Address", - Meetup: "Meetup", -} as const; - -type DeliveryMethod = - (typeof DeliveryMethodEnum)[keyof typeof DeliveryMethodEnum]; +import { useOrderFiltering } from "~/hooks/user/useOrderFiltering"; +import { + type DeliveryMethod, + DeliveryMethodEnum, + type SalesStatus, + SalesStatusEnum, +} from "~/types/user/order"; const filtersSchema = z.object({ statusPaid: z.boolean().optional(), @@ -81,94 +70,40 @@ const mockedOrders = [ }, ]; +const filtersDefaults = { + statusPaid: false, + statusPrepared: false, + statusShipped: false, + statusDelivered: false, + deliveryAddress: false, + deliveryMeetup: false, +}; + export default function MySales() { - const [searchTerm, setSearchTerm] = useState(""); - const [isFiltersModalOpen, setIsFiltersModalOpen] = useState(false); - const [filteredOrders, setFilteredOrders] = useState(mockedOrders); - const [activeFilters, setActiveFilters] = useState({}); + const { + searchTerm, + setSearchTerm, + isFiltersModalOpen, + openFiltersModal, + closeFiltersModal, + filteredOrders, + applyFilters, + } = useOrderFiltering({ + orders: mockedOrders, + searchKey: "buyerName", + filters: filtersDefaults, + }); const router = useRouter(); - const openFiltersModal = () => { - setIsFiltersModalOpen(true); - }; - - const closeFiltersModal = () => { - setIsFiltersModalOpen(false); - }; - const { control, handleSubmit } = useForm({ resolver: zodResolver(filtersSchema), - defaultValues: { - statusPaid: false, - statusPrepared: false, - statusShipped: false, - statusDelivered: false, - deliveryAddress: false, - deliveryMeetup: false, - }, + defaultValues: filtersDefaults, }); - const onSubmit = (data: FormValues) => { - console.log(data); - setActiveFilters(data); - closeFiltersModal(); - }; - const handleItemClick = (id: string) => { router.push(`/user/my-sales/${id}`); }; - useEffect(() => { - const newFilteredOrders = mockedOrders - .map((orderGroup) => ({ - ...orderGroup, - items: orderGroup.items.filter((item) => { - const matchesSearch = searchTerm - ? item.buyerName.toLowerCase().includes(searchTerm.toLowerCase()) - : true; - - const activeStatusFilters = [ - activeFilters.statusPaid, - activeFilters.statusPrepared, - activeFilters.statusShipped, - activeFilters.statusDelivered, - ]; - - const matchesStatus = !activeStatusFilters.some(Boolean) - ? true - : (activeFilters.statusPaid && - item.status === SalesStatusEnum.Paid) ?? - (activeFilters.statusPrepared && - item.status === SalesStatusEnum.Prepared) ?? - (activeFilters.statusShipped && - item.status === SalesStatusEnum.Shipped) ?? - (activeFilters.statusDelivered && - item.status === SalesStatusEnum.Delivered); - - const activeDeliveryFilters = [ - activeFilters.deliveryAddress, - activeFilters.deliveryMeetup, - ]; - - const matchesDelivery = !activeDeliveryFilters.some(Boolean) - ? true - : (activeFilters.deliveryAddress && - item.delivery === DeliveryMethodEnum.Address) ?? - (activeFilters.deliveryMeetup && - item.delivery === DeliveryMethodEnum.Meetup); - - return matchesSearch && matchesStatus && matchesDelivery; - }), - })) - .filter((orderGroup) => orderGroup.items.length > 0); - - if (!searchTerm && !Object.values(activeFilters).some(Boolean)) { - setFilteredOrders(mockedOrders); - } else { - setFilteredOrders(newFilteredOrders); - } - }, [searchTerm, activeFilters]); - return (
@@ -203,7 +138,7 @@ export default function MySales() { handleItemClick(order.id)} /> @@ -218,7 +153,7 @@ export default function MySales() {
- +

Delivery method

diff --git a/apps/web/src/hooks/user/useOrderFiltering.tsx b/apps/web/src/hooks/user/useOrderFiltering.tsx new file mode 100644 index 0000000..e806df4 --- /dev/null +++ b/apps/web/src/hooks/user/useOrderFiltering.tsx @@ -0,0 +1,112 @@ +import { useEffect, useState } from "react"; +import { + DeliveryMethodEnum, + type Order, + SalesStatusEnum, +} from "~/types/user/order"; + +interface UseOrderFilteringProps { + orders: Order[]; + searchKey: "sellerName" | "buyerName"; + filters: Record; +} + +export function useOrderFiltering({ + orders, + searchKey, + filters, +}: UseOrderFilteringProps) { + const [searchTerm, setSearchTerm] = useState(""); + const [isFiltersModalOpen, setIsFiltersModalOpen] = useState(false); + const [filteredOrders, setFilteredOrders] = useState(orders); + const [activeFilters, setActiveFilters] = + useState>(filters); + + const openFiltersModal = () => setIsFiltersModalOpen(true); + const closeFiltersModal = () => setIsFiltersModalOpen(false); + + const applyFilters = (newFilters: Record) => { + setActiveFilters(newFilters); + closeFiltersModal(); + }; + + useEffect(() => { + const newFilteredOrders = orders + .map((orderGroup) => ({ + ...orderGroup, + items: orderGroup.items.filter((item) => { + const matchesSearch = searchTerm + ? item[searchKey]?.toLowerCase().includes(searchTerm.toLowerCase()) + : true; + + const activeStatusFilters = [ + activeFilters.statusPaid, + activeFilters.statusPrepared, + activeFilters.statusShipped, + activeFilters.statusDelivered, + ]; + + const matchesStatus = activeStatusFilters.some(Boolean) + ? [ + { + filter: activeFilters.statusPaid, + status: SalesStatusEnum.Paid, + }, + { + filter: activeFilters.statusPrepared, + status: SalesStatusEnum.Prepared, + }, + { + filter: activeFilters.statusShipped, + status: SalesStatusEnum.Shipped, + }, + { + filter: activeFilters.statusDelivered, + status: SalesStatusEnum.Delivered, + }, + ].some(({ filter, status }) => filter && item.status === status) + : true; + + const activeDeliveryFilters = [ + activeFilters.deliveryAddress, + activeFilters.deliveryMeetup, + ]; + + const matchesDelivery = activeDeliveryFilters.some(Boolean) + ? [ + { + filter: activeFilters.deliveryAddress, + deliveryMethod: DeliveryMethodEnum.Address, + }, + { + filter: activeFilters.deliveryMeetup, + deliveryMethod: DeliveryMethodEnum.Meetup, + }, + ].some( + ({ filter, deliveryMethod }) => + filter && item.delivery === deliveryMethod, + ) + : true; + + return matchesSearch && matchesStatus && matchesDelivery; + }), + })) + .filter((orderGroup) => orderGroup.items.length > 0); + if (!searchTerm && !Object.values(activeFilters).some(Boolean)) { + setFilteredOrders(orders); + } else { + setFilteredOrders(newFilteredOrders); + } + }, [orders, searchKey, searchTerm, activeFilters]); + + return { + searchTerm, + setSearchTerm, + isFiltersModalOpen, + openFiltersModal, + closeFiltersModal, + activeFilters, + filteredOrders, + applyFilters, + }; +} diff --git a/apps/web/src/types/user/order.ts b/apps/web/src/types/user/order.ts new file mode 100644 index 0000000..f3685eb --- /dev/null +++ b/apps/web/src/types/user/order.ts @@ -0,0 +1,30 @@ +export enum SalesStatusEnum { + Paid = "Paid", + Prepared = "Prepared", + Shipped = "Shipped", + Delivered = "Delivered", +} + +export type SalesStatus = + (typeof SalesStatusEnum)[keyof typeof SalesStatusEnum]; + +export enum DeliveryMethodEnum { + Address = "Address", + Meetup = "Meetup", +} + +export type DeliveryMethod = keyof typeof DeliveryMethodEnum; + +export interface OrderItem { + id: string; + productName: string; + status: SalesStatus; + sellerName?: string; + buyerName?: string; + delivery?: DeliveryMethod; +} + +export interface Order { + date: string; + items: OrderItem[]; +}