sort foods

This commit is contained in:
hamid zarghami
2025-11-25 12:49:38 +03:30
parent 69dfb50267
commit 3d6a8c4877
7 changed files with 84 additions and 60 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -1,12 +1,14 @@
'use client'; 'use client';
import AnimatedBottomSheet from "@/components/bottomsheet/AnimatedBottomSheet"; import AnimatedBottomSheet from "@/components/bottomsheet/AnimatedBottomSheet";
import clsx from "clsx";
import React from "react"; import React from "react";
type MenuSortingDrawerProps = { type MenuSortingDrawerProps = {
visible: boolean; visible: boolean;
onClose: () => void; onClose: () => void;
sortings: string[]; sortings: ReadonlyArray<string>;
activeIndex: number;
onSelect: (index: number) => void; onSelect: (index: number) => void;
tMenu: (key: string) => string; tMenu: (key: string) => string;
}; };
@@ -15,6 +17,7 @@ const MenuSortingDrawer = ({
visible, visible,
onClose, onClose,
sortings, sortings,
activeIndex,
onSelect, onSelect,
tMenu, tMenu,
}: MenuSortingDrawerProps) => { }: MenuSortingDrawerProps) => {
@@ -25,20 +28,23 @@ const MenuSortingDrawer = ({
inDelay={150} inDelay={150}
onClick={onClose} onClick={onClose}
> >
<div className="px-8.5 py-10 justify-between"> <div className="px-8.5 py-10 flex flex-col gap-2">
{sortings.map((value, index) => ( {sortings.map((value, index) => {
<div key={value}> const isActive = index === activeIndex;
<div return (
<button
key={value}
type="button"
onClick={() => onSelect(index)} onClick={() => onSelect(index)}
className="text-sm2 font-normal cursor-pointer" className={clsx(
"flex items-center justify-between py-3 text-sm2 transition-colors",
isActive ? "text-primary font-semibold" : "text-foreground"
)}
> >
{tMenu(`MenuSortingDrawer.Options.${value}`)} <span>{tMenu(`MenuSortingDrawer.Options.${value}`)}</span>
</div> </button>
{index < sortings.length - 1 && ( );
<hr className="border-white/40 dark:border-border mb-4 mt-4" /> })}
)}
</div>
))}
</div> </div>
</AnimatedBottomSheet> </AnimatedBottomSheet>
); );
+43 -30
View File
@@ -1,5 +1,4 @@
'use client'; 'use client';
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import SearchBox from "@/components/input/SearchBox"; import SearchBox from "@/components/input/SearchBox";
import TextAlignIcon from "@/components/icons/TextAlignIcon"; import TextAlignIcon from "@/components/icons/TextAlignIcon";
@@ -18,29 +17,18 @@ import MenuSortingDrawer from "@/app/[name]/(Main)/components/MenuSortingDrawer"
import { useGetCategories, useGetFoods } from "./hooks/useMenuData"; import { useGetCategories, useGetFoods } from "./hooks/useMenuData";
import type { Food, Category } from "./types/Types"; import type { Food, Category } from "./types/Types";
const sortings = [ const sortings = ["PopularityDescending", "RateDescending", "PriceAscending", "PriceDescending"] as const;
"PopularityDescending",
"DateDescending",
"PriceAscending",
"RateDescending",
"PriceDescending"
];
const MenuIndex = () => { const MenuIndex = () => {
const { data: foodsData } = useGetFoods(); const { data: foodsData } = useGetFoods();
const { data: categoriesData } = useGetCategories(); const { data: categoriesData } = useGetCategories();
const foods = useMemo<Food[]>(() => foodsData?.data || [], [foodsData?.data]); const foods = useMemo<Food[]>(() => foodsData?.data || [], [foodsData?.data]);
const categories = useMemo<Category[]>(() => categoriesData?.data || [], [categoriesData?.data]); const categories = useMemo<Category[]>(() => categoriesData?.data || [], [categoriesData?.data]);
const { t: tCommon } = useTranslation('common'); const { t: tCommon } = useTranslation('common');
const { t: tMenu } = useTranslation('menu', { const { t: tMenu } = useTranslation('menu', { keyPrefix: "Menu" });
keyPrefix: "Menu"
});
const { state: filterModal, toggle: toggleFilterModal } = useToggle(); const { state: filterModal, toggle: toggleFilterModal } = useToggle();
const { state: sortingModal, toggle: toggleSortingModal } = useToggle(); const { state: sortingModal, toggle: toggleSortingModal } = useToggle();
const [sorting, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
const [, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
const [search, setSearch] = useQueryState("q", { defaultValue: '' }); const [search, setSearch] = useQueryState("q", { defaultValue: '' });
const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' }); const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' });
const [selectedIngredients, setSelectedIngredients] = useQueryState('ingredients', { defaultValue: '0' }); const [selectedIngredients, setSelectedIngredients] = useQueryState('ingredients', { defaultValue: '0' });
@@ -81,9 +69,15 @@ const MenuIndex = () => {
setSelectedCategory(String(index)); setSelectedCategory(String(index));
}, [setSelectedCategory]); }, [setSelectedCategory]);
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 = (index: number) => {
setSorting(() => String(index)); setSorting(() => String(index));
} toggleSortingModal();
};
const changeSelectedIngredients = useCallback((value: string) => { const changeSelectedIngredients = useCallback((value: string) => {
setSelectedIngredients(() => value); setSelectedIngredients(() => value);
@@ -95,22 +89,36 @@ const MenuIndex = () => {
const filteredReceiptItems = useMemo(() => { const filteredReceiptItems = useMemo(() => {
if (!foods.length) return []; if (!foods.length) return [];
const lowerSearch = search.toLowerCase(); const lowerSearch = search.toLowerCase();
const selectedCatId = selectedCategory === '0' ? null : categories[+selectedCategory - 1]?.id; const selectedCatId =
selectedCategory === "0" ? null : categories[+selectedCategory - 1]?.id;
return foods.filter( const filtered = foods.filter((item) => {
(item) => { const matchesCategory =
const matchesCategory = selectedCategory === "0" || item.category?.id === selectedCatId;
selectedCategory === '0' || item.category?.id === selectedCatId; const itemName = item.name || item.title || item.foodName || "";
const itemName = item.name || item.title || item.foodName || ''; const matchesSearch =
const matchesSearch = !search || itemName.toLowerCase().includes(lowerSearch); !search || itemName.toLowerCase().includes(lowerSearch);
return matchesCategory && matchesSearch; return matchesCategory && matchesSearch;
});
const sortingKey = sortings[Number(sorting)] || sortings[0];
return [...filtered].sort((a, b) => {
switch (sortingKey) {
case "PopularityDescending":
return (b.points ?? 0) - (a.points ?? 0);
case "RateDescending":
return (b.rate ?? 0) - (a.rate ?? 0);
case "PriceAscending":
return (a.price ?? 0) - (b.price ?? 0);
case "PriceDescending":
return (b.price ?? 0) - (a.price ?? 0);
default:
return 0;
} }
); });
}, [selectedCategory, search, foods, categories]); }, [selectedCategory, search, foods, categories, sorting]);
return ( return (
<div className="flex flex-col gap-4 items-center pt-8 mb-8" ref={wrapperRef}> <div className="flex flex-col gap-4 items-center pt-8 mb-8" ref={wrapperRef}>
<div className="w-full"> <div className="w-full">
@@ -132,8 +140,12 @@ const MenuIndex = () => {
<Candle2 className="stroke-foreground" size={16} /> <Candle2 className="stroke-foreground" size={16} />
<span className="text-xs leading-5 font-medium">{tMenu('MenuFilterDrawer.Label')}</span> <span className="text-xs leading-5 font-medium">{tMenu('MenuFilterDrawer.Label')}</span>
</button> </button>
<button onClick={toggleSortingModal} className="rounded-lg h-8 bg-container p-1.5"> <button
onClick={toggleSortingModal}
className="rounded-xl h-8 bg-container ps-4 pe-2 py-1.5 inline-flex items-center gap-2"
>
<TextAlignIcon className="text-foreground" /> <TextAlignIcon className="text-foreground" />
<span className="text-xs leading-5 font-medium">{activeSortingLabel}</span>
</button> </button>
</div> </div>
</div> </div>
@@ -183,6 +195,7 @@ const MenuIndex = () => {
visible={sortingModal} visible={sortingModal}
onClose={toggleSortingModal} onClose={toggleSortingModal}
sortings={sortings} sortings={sortings}
activeIndex={activeSortingIndex}
onSelect={changeSorting} onSelect={changeSorting}
tMenu={tMenu} tMenu={tMenu}
/> />
+10 -13
View File
@@ -2,11 +2,12 @@ import PreferenceWrapper from '@/components/wrapper/PreferenceWrapper'
import React from 'react' import React from 'react'
import type { Metadata, Viewport } from 'next' import type { Metadata, Viewport } from 'next'
type LayoutParams = { name: string }
export async function generateMetadata ({ export async function generateMetadata ({
params params
}: { }: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any params: Promise<LayoutParams>
params: any
}): Promise<Metadata> { }): Promise<Metadata> {
const { name } = await params const { name } = await params
return { return {
@@ -16,21 +17,17 @@ export async function generateMetadata ({
} }
} }
export async function generateViewport ({}: { export async function generateViewport (): Promise<Viewport> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
params: any
}): Promise<Viewport> {
return { return {
themeColor: '#F4F5F9' themeColor: '#F4F5F9'
} }
} }
type Props = { export default function Layout ({
children: React.ReactElement children
} }: {
children: React.ReactNode
function layout ({ children }: Props) { params: Promise<LayoutParams>
}): React.ReactNode {
return <PreferenceWrapper>{children}</PreferenceWrapper> return <PreferenceWrapper>{children}</PreferenceWrapper>
} }
export default layout
+3
View File
@@ -1,4 +1,7 @@
export enum AUTH_STEP { export enum AUTH_STEP {
ENTER_NUMBER = 0, ENTER_NUMBER = 0,
ENTER_OTP = 1, ENTER_OTP = 1,
ENTER_CREATE_PASSWORD = 2,
ENTER_CURRENT_PASSWORD = 3,
ENTER_RESET_PASSWORD = 4
} }
@@ -6,6 +6,7 @@ import { AUTH_PAGE_ELEMENT, AUTH_STEP } from '@/enums'
import { LoginOTPRequestType } from '@/app/auth/login/types/Types' import { LoginOTPRequestType } from '@/app/auth/login/types/Types'
import Image from 'next/image' import Image from 'next/image'
import React, { useState } from 'react' import React, { useState } from 'react'
import type { ChangeEvent } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
@@ -15,9 +16,14 @@ import { toast } from '@/components/Toast'
import StepOtp from './StepOtp' import StepOtp from './StepOtp'
import { extractErrorMessage } from '@/lib/func' import { extractErrorMessage } from '@/lib/func'
type StepEnterNumberProps = {
pending?: boolean
onChange?: (event: ChangeEvent<HTMLInputElement>) => void
value?: string
}
function StepEnterNumber (_props: StepEnterNumberProps = {}) {
function StepEnterNumber() { void _props
const { t } = useTranslation('auth') const { t } = useTranslation('auth')
const { name } = useParams() const { name } = useParams()
-1
View File
@@ -29,7 +29,6 @@
"Options": { "Options": {
"PopularityDescending": "محبوب ترین", "PopularityDescending": "محبوب ترین",
"RateDescending": "بالاترین امتیاز", "RateDescending": "بالاترین امتیاز",
"DateDescending": "جدیدترین",
"PriceAscending": "ارزان ترین", "PriceAscending": "ارزان ترین",
"PriceDescending": "گران ترین" "PriceDescending": "گران ترین"
} }