merge filter and sort

This commit is contained in:
hamid zarghami
2026-02-14 11:02:09 +03:30
parent ebb3b7d079
commit a8deff9267
6 changed files with 88 additions and 91 deletions
@@ -1,10 +1,11 @@
'use client';
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useRef } from "react";
import Button from "@/components/button/PrimaryButton";
import AnimatedBottomSheet from "@/components/bottomsheet/AnimatedBottomSheet";
import { Switch } from "@/components/ui/switch";
import { TicketPercentIcon } from "lucide-react";
import { TicketPercentIcon, ChevronDown } from "lucide-react";
import clsx from "clsx";
type MenuFilterDrawerProps = {
visible: boolean;
@@ -12,6 +13,9 @@ type MenuFilterDrawerProps = {
searchQuery: string;
onSearchChange: (value: string) => void;
onApply: () => void;
sortings: ReadonlyArray<string>;
activeSortIndex: number;
onSortChange: (index: number) => void;
tMenu: (key: string) => string;
};
@@ -21,15 +25,34 @@ const MenuFilterDrawer = ({
searchQuery,
onSearchChange,
onApply,
sortings,
activeSortIndex,
onSortChange,
tMenu,
}: MenuFilterDrawerProps) => {
const [tempQuery, setTempQuery] = useState(searchQuery);
const [tempSortIndex, setTempSortIndex] = useState(activeSortIndex);
const [sortSelectOpen, setSortSelectOpen] = useState(false);
const sortSelectRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (visible) {
setTempQuery(searchQuery);
setTempSortIndex(activeSortIndex);
setSortSelectOpen(false);
}
}, [visible, searchQuery]);
}, [visible, searchQuery, activeSortIndex]);
useEffect(() => {
if (!sortSelectOpen || !visible) return;
const handleClickOutside = (e: MouseEvent) => {
if (sortSelectRef.current && !sortSelectRef.current.contains(e.target as Node)) {
setSortSelectOpen(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, [sortSelectOpen, visible]);
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setTempQuery(e.target.value);
@@ -37,12 +60,14 @@ const MenuFilterDrawer = ({
const handleApply = () => {
onSearchChange(tempQuery);
onSortChange(tempSortIndex);
onApply();
onClose();
};
const handleCancel = () => {
setTempQuery(searchQuery);
setTempSortIndex(activeSortIndex);
onClose();
};
@@ -54,6 +79,7 @@ const MenuFilterDrawer = ({
onClick={onClose}
>
<div className="px-6 mt-5">
{/* فیلتر: جستجو */}
<div className="flex flex-col gap-2">
<label className="text-sm2 text-foreground">
{tMenu("MenuFilterDrawer.SelectContent.Label")}
@@ -66,6 +92,7 @@ const MenuFilterDrawer = ({
className="w-full h-11 px-3 py-2.5 rounded-normal border border-border bg-container/29 focus:outline-none focus:ring-2 focus:ring-black text-xs!"
/>
</div>
{/* فیلتر: دارای کد تخفیف */}
<div className="flex w-full mt-[23px] h-11 items-center justify-between gap-2 px-3 py-4 relative bg-container/29 rounded-xl border border-solid border-border cursor-pointer focus:outline-none focus:ring-2 focus:ring-black">
<span className="inline-flex items-center gap-2.5 text-sm2">
<TicketPercentIcon size={16} />
@@ -73,6 +100,46 @@ const MenuFilterDrawer = ({
</span>
<Switch />
</div>
{/* مرتب‌سازی - Select با کلیک باز می‌شود */}
<div className="mt-6" ref={sortSelectRef}>
<label className="text-sm2 text-foreground block mb-3">
{tMenu("MenuSortingDrawer.Heading")}
</label>
<button
type="button"
onClick={() => setSortSelectOpen((prev) => !prev)}
className="w-full h-11 px-3 py-2.5 rounded-xl border border-border bg-container/29 flex items-center justify-between gap-2 text-right text-sm2 text-foreground focus:outline-none focus:ring-2 focus:ring-black"
>
<span>{tMenu(`MenuSortingDrawer.Options.${sortings[tempSortIndex]}`)}</span>
<ChevronDown
className={clsx("w-4 h-4 shrink-0 transition-transform", sortSelectOpen && "rotate-180")}
/>
</button>
{sortSelectOpen && (
<div className="mt-1 rounded-xl border border-border bg-container/29 overflow-hidden">
{sortings.map((value, index) => {
const isActive = index === tempSortIndex;
return (
<button
key={value}
type="button"
onClick={() => {
setTempSortIndex(index);
setSortSelectOpen(false);
}}
className={clsx(
"flex items-center justify-between w-full py-3 px-3 text-sm2 transition-colors text-right",
isActive ? "text-primary font-semibold bg-primary/10" : "text-foreground hover:bg-muted/50"
)}
>
<span>{tMenu(`MenuSortingDrawer.Options.${value}`)}</span>
</button>
);
})}
</div>
)}
</div>
</div>
<hr className="text-white/40 mt-12" />
<div className="px-9 pt-6 flex justify-between gap-[22px]">
@@ -1,54 +0,0 @@
'use client';
import AnimatedBottomSheet from "@/components/bottomsheet/AnimatedBottomSheet";
import clsx from "clsx";
import React from "react";
type MenuSortingDrawerProps = {
visible: boolean;
onClose: () => void;
sortings: ReadonlyArray<string>;
activeIndex: number;
onSelect: (index: number) => void;
tMenu: (key: string) => string;
};
const MenuSortingDrawer = ({
visible,
onClose,
sortings,
activeIndex,
onSelect,
tMenu,
}: MenuSortingDrawerProps) => {
return (
<AnimatedBottomSheet
title={tMenu("MenuSortingDrawer.Heading")}
visible={visible}
inDelay={150}
onClick={onClose}
>
<div className="px-8.5 py-10 flex flex-col gap-2">
{sortings.map((value, index) => {
const isActive = index === activeIndex;
return (
<button
key={value}
type="button"
onClick={() => onSelect(index)}
className={clsx(
"flex items-center justify-between py-3 text-sm2 transition-colors",
isActive ? "text-primary font-semibold" : "text-foreground"
)}
>
<span>{tMenu(`MenuSortingDrawer.Options.${value}`)}</span>
</button>
);
})}
</div>
</AnimatedBottomSheet>
);
};
export default MenuSortingDrawer;
+11 -27
View File
@@ -5,7 +5,6 @@ import TextAlignIcon from "@/components/icons/TextAlignIcon";
import VerticalScrollView from "@/components/listview/VerticalScrollView";
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
import MenuItem from "@/components/listview/MenuItem";
import { Candle2 } from "iconsax-react";
import { useQueryState } from "next-usequerystate";
import clsx from "clsx";
import { motion } from "framer-motion";
@@ -13,7 +12,6 @@ import { useTranslation } from "react-i18next";
import useToggle from "@/hooks/helpers/useToggle";
import CategoryScroll from "@/app/[name]/(Main)/components/CategoryScroll";
import MenuFilterDrawer from "@/app/[name]/(Main)/components/MenuFilterDrawer";
import MenuSortingDrawer from "@/app/[name]/(Main)/components/MenuSortingDrawer";
import MenuSkeleton from "@/app/[name]/(Main)/components/MenuSkeleton";
import { useGetCategories, useGetProducts } from "./hooks/useMenuData";
import type { Product, Category } from "./types/Types";
@@ -29,8 +27,7 @@ const MenuIndex = () => {
const isLoading = productsLoading || categoriesLoading;
const { t: tCommon } = useTranslation('common');
const { t: tMenu } = useTranslation('menu', { keyPrefix: "Menu" });
const { state: filterModal, toggle: toggleFilterModal } = useToggle();
const { state: sortingModal, toggle: toggleSortingModal } = useToggle();
const { state: filterSortModal, toggle: toggleFilterSortModal } = useToggle();
const [sorting, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
const [search, setSearch] = useQueryState("q", { defaultValue: '' });
const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' });
@@ -85,13 +82,10 @@ const MenuIndex = () => {
const sortingIndex = Number(sorting);
const activeSortingIndex = Number.isNaN(sortingIndex) ? 0 : sortingIndex;
const activeSortingKey = sortings[activeSortingIndex] ?? sortings[0];
const activeSortingLabel = tMenu(`MenuSortingDrawer.Options.${activeSortingKey}`);
const changeSorting = (index: number) => {
const changeSorting = useCallback((index: number) => {
setSorting(() => String(index));
toggleSortingModal();
};
}, [setSorting]);
const changeFilterSearch = useCallback((value: string) => {
setFilterSearch(value);
@@ -105,7 +99,7 @@ const MenuIndex = () => {
const filtered = products.filter((item) => {
const matchesCategory = !selectedCatId || item.category?.id === selectedCatId;
const itemName = item.name || item.title || item.foodName || item.productName || "";
const itemName = item.name ?? item.title;
const matchesSearch =
!search || itemName.toLowerCase().includes(lowerSearch);
@@ -165,17 +159,13 @@ const MenuIndex = () => {
<span className="sm:text-base text-sm font-medium">
{selectedCategory === '0' ? '' : categories.find(c => c.id === selectedCategory)?.title || ''}
</span>
<div className="inline-flex min-w-[247px] gap-2 justify-around items-center">
<button onClick={toggleFilterModal} className="rounded-xl h-8 bg-container ps-4 pe-2 py-1.5 inline-flex items-center justify-between gap-[7px]">
<Candle2 className="stroke-foreground" size={16} />
<span className="text-xs leading-5 font-medium">{tMenu('MenuFilterDrawer.Label')}</span>
</button>
<div className="inline-flex gap-2 justify-end items-center">
<button
onClick={toggleSortingModal}
onClick={toggleFilterSortModal}
className="rounded-xl h-8 bg-container ps-4 pe-2 py-1.5 inline-flex items-center gap-2"
>
<TextAlignIcon className="text-foreground" />
<span className="text-xs leading-5 font-medium">{activeSortingLabel}</span>
<span className="text-xs leading-5 font-medium">{tMenu('MenuFilterDrawer.Label')}</span>
</button>
</div>
</div>
@@ -212,20 +202,14 @@ const MenuIndex = () => {
</section>
<MenuFilterDrawer
visible={filterModal}
onClose={toggleFilterModal}
visible={filterSortModal}
onClose={toggleFilterSortModal}
searchQuery={filterSearch}
onSearchChange={changeFilterSearch}
onApply={() => { }}
tMenu={tMenu}
/>
<MenuSortingDrawer
visible={sortingModal}
onClose={toggleSortingModal}
sortings={sortings}
activeIndex={activeSortingIndex}
onSelect={changeSorting}
activeSortIndex={activeSortingIndex}
onSortChange={changeSorting}
tMenu={tMenu}
/>
</div>
+4 -4
View File
@@ -30,8 +30,8 @@ export interface ProductCategory {
title: string;
isActive: boolean;
shop: string;
avatarUrl: string;
order: number;
avatarUrl: string | null;
order: number | null;
}
export interface ProductVariant {
@@ -51,10 +51,10 @@ export interface Product {
deletedAt: string | null;
shop: string;
category: ProductCategory;
attribute: string;
attribute: string | null;
title: string;
desc: string;
order: number;
order: number | null;
isActive: boolean;
images: string[];
score: number | null;
+2 -2
View File
@@ -1,8 +1,8 @@
{
"Menu": {
"MenuFilterDrawer": {
"Label": "فیلتر بر اساس",
"Heading": "فیلتر ها",
"Label": "فیلتر و مرتب‌سازی",
"Heading": "فیلتر و مرتب‌سازی",
"SelectContent": {
"Label": "جستجو",
"SearchLabel": "جستجو ...",
+1 -1
View File
File diff suppressed because one or more lines are too long