Files
dmenu-plus-front/src/app/[locale]/[name]/page.tsx
T
Mahyar Khanbolooki 1b3fac2f43 add: locale and i18n
2025-07-24 03:14:21 +03:30

363 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import SearchBox from "@/components/input/SearchBox";
import CategoryItemRenderer from "@/components/listview/CategoryItemRenderer";
import HorizontalScrollView from "@/components/listview/HorizontalScrollView";
import Image from "next/image";
import EqualizerIcon from "@/components/icons/EqualizerIcon";
import TextAlignIcon from "@/components/icons/TextAlignIcon";
import VerticalScrollView from "@/components/listview/VerticalScrollView";
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
import React from "react";
import MenuItem from "@/components/listview/MenuItem";
import Button from "@/components/button/PrimaryButton";
import AnimatedBottomSheet from "@/components/bottomsheet/AnimatedBottomSheet";
import { Switch } from "@/components/ui/switch";
import { TicketPercentIcon } from "lucide-react";
import { MedalStar } from 'iconsax-react'
import { useQueryState } from 'next-usequerystate'
import ComboBox, { DropdownOption } from "@/components/combobox/ComboBox";
import clsx from "clsx";
import CategorySmallItemRenderer from "@/components/listview/CategorySmallItemRenderer";
import { motion } from "framer-motion";
const categories = new Array(13).fill({ title: "خوراک", icon: "" });
const foods = [
{
id: 1,
category: 0,
price: "69.000",
name: "کباب چوبی ژیوان )سیخ چوبی(",
contains: "فیله گوساله / سس سفید / قارچ و سیب زمینی/ پنیر پارمزان"
},
{
id: 2,
category: 1,
price: "49.000",
name: "برگر گوشت گوساله",
contains: "گوشت گوساله / پنیر چدار / کاهو / گوجه"
},
{
id: 3,
category: 2,
price: "29.000",
name: "ساندویچ مرغ",
contains: "مرغ / کاهو / گوجه / سس مایونز"
},
{
id: 4,
category: 0,
price: "89.000",
name: "کباب ماهی",
contains: "ماهی / سس لیمو / قارچ و سیب زمینی"
},
{
id: 5,
category: 1,
price: "39.000",
name: "سالاد فصل",
contains: "کاهو / گوجه / خیار / پنیر فتا"
},
{
id: 6,
category: 3,
price: "59.000",
name: "پاستا گوشت",
contains: "گوشت گوساله / پاستا / سس گوجه"
},
{
id: 7,
category: 2,
price: "49.000",
name: "ساندویچ تن ماهی",
contains: "تن ماهی / کاهو / گوجه / سس مایونز"
},
{
id: 8,
category: 0,
price: "79.000",
name: "کباب گوشت گوسفندی",
contains: "گوشت گوسفندی / سس سفید / قارچ و سیب زمینی"
},
{
id: 9,
category: 1,
price: "29.000",
name: "برگر مرغ",
contains: "مرغ / پنیر چدار / کاهو / گوجه"
},
{
id: 10,
category: 3,
price: "69.000",
name: "سالاد میگو",
contains: "میگو / کاهو / گوجه / خیار"
},
{
id: 11,
category: 2,
price: "39.000",
name: "ساندویچ شاورما",
contains: "مرغ / کاهو / گوجه / سس تارتار"
},
{
id: 12,
category: 0,
price: "99.000",
name: "کباب گوشت بوقلمون",
contains: "گوشت بوقلمون / سس سفید / قارچ و سیب زمینی"
},
{
id: 13,
category: 1,
price: "59.000",
name: "برگر ماهی",
contains: "ماهی / پنیر چدار / کاهو / گوجه"
},
{
id: 14,
category: 3,
price: "49.000",
name: "سالاد مرغ",
contains: "مرغ / کاهو / گوجه / خیار"
}
];
const sortings = [
'محبوب ترین',
'بالاترین امتیاز',
'جدید ترین',
'ارزان ترین',
'گران ترین',
]
const contents: Array<DropdownOption> = [
{ id: '0', title: 'گیاهی' },
{ id: '1', title: 'حیوانی' }
]
const shippings: Array<DropdownOption> = [
{ id: '0', title: 'ارسال با پیک', label: '' },
]
const MenuIndex = () => {
const [filterModal, setFilterModal] = useState(false);
const [sortingModal, setSortingModal] = useState(false);
const [, setSorting] = useQueryState('sortBy', { defaultValue: '0' });
const [search, setSearch] = useQueryState("q", { defaultValue: '' });
const [selectedCategory, setSelectedCategory] = useQueryState('category', { defaultValue: '0' });
const [selectedContentId, setSelectedContentId] = useQueryState('contentType', { defaultValue: '0' });
const [selectedShippingId, setSelectedShippingId] = useQueryState('shippingType', { defaultValue: '0' });
const smallCategoriesRef: React.RefObject<HTMLDivElement | null> = useRef(null);
const wrapperRef: React.RefObject<HTMLDivElement | null> = useRef(null);
const [smallCategoriesVisible, setSmallCategoriesVisibility] = useState(false);
const onScroll = useCallback(() => {
if (!wrapperRef?.current?.parentElement?.scrollTop || !smallCategoriesRef.current?.offsetTop) return;
if (wrapperRef.current.parentElement.scrollTop >= smallCategoriesRef.current.offsetTop) {
setSmallCategoriesVisibility(true);
} else {
setSmallCategoriesVisibility(false);
}
}, [wrapperRef, smallCategoriesRef]);
useEffect(() => {
if (!wrapperRef.current) return;
const parent = wrapperRef.current.parentElement;
if (!parent) return;
parent.addEventListener('scroll', onScroll);
return () => {
parent.removeEventListener('scroll', onScroll);
}
}, [onScroll]);
const updateSearch = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setSearch(e.target.value);
}, [setSearch]);
const updateCategory = useCallback((id: number) => {
setSelectedCategory(String(id));
}, [setSelectedCategory]);
const toggleFilterModal = useCallback(() => {
setFilterModal((state) => !state);
}, []);
const toggleSortingModal = useCallback(() => {
setSortingModal((state) => !state);
}, []);
const changeSorting = (index: number) => {
setSorting(() => String(index));
}
const changeSelectedContent = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
setSelectedContentId(() => contents[index]?.id || contents[0].id);
}
const changeSelectedShipping = (e: React.MouseEvent<HTMLDivElement, MouseEvent>, index: number) => {
setSelectedShippingId(() => contents[index]?.id || contents[0].id);
}
const filteredReceiptItems = useMemo(() => {
const lowerSearch = search.toLowerCase();
return foods.filter(
(item) =>
item.category === +selectedCategory &&
item.name.toLowerCase().includes(lowerSearch)
);
}, [selectedCategory, search]);
return (
<div className="flex flex-col gap-4 items-center pt-8" ref={wrapperRef}>
<div className="w-full">
<SearchBox value={search} onChange={updateSearch} />
<HorizontalScrollView className="w-full noscrollbar mt-4! py-4!">
{categories.map((item, index) => (
<CategoryItemRenderer
key={index}
className={clsx(
index === +selectedCategory && "bg-white!",
)}
onClick={() => updateCategory(index)}
>
<Image
priority
src="/assets/images/food-image.png"
width={32}
height={32}
alt="category image"
/>
<span className="text-xs text-black">{item.title}</span>
</CategoryItemRenderer>
))}
</HorizontalScrollView>
</div>
<section className="w-full">
<div className="flex justify-between items-center relative" ref={smallCategoriesRef} >
<span className="text-base font-medium">
{categories[+selectedCategory]?.title}
</span>
<div className="inline-flex gap-2 justify-around items-center">
<button onClick={toggleFilterModal} className="rounded-xl h-8 bg-white ps-4 pe-2 py-1.5 inline-flex items-center justify-between gap-[7px]">
<EqualizerIcon />
<span className="text-xs leading-5 font-medium">فیلتر بر اساس</span>
</button>
<button onClick={toggleSortingModal} className="rounded-lg h-8 bg-white p-1.5">
<TextAlignIcon />
</button>
</div>
</div>
<motion.div
initial={{ y: -200 }}
animate={{ y: smallCategoriesVisible ? 0 : -200, opacity: smallCategoriesVisible ? 1 : 0 }}
transition={{ duration: .1 }}
className={clsx(
'fixed left-0 z-10 top-0 px-4 pt-16 bg-[#F4F5F9CC] backdrop-blur-[44px] right-0',
``
)}>
<HorizontalScrollView className="w-full noscrollbar py-4!">
{categories.map((item, index) => (
<CategorySmallItemRenderer
key={index}
className={clsx(
index === +selectedCategory && "bg-white!",
)}
onClick={() => updateCategory(index)}
>
<Image
priority
src="/assets/images/food-image.png"
width={24}
height={24}
alt="category image"
/>
<span className="text-xs text-black">{item.title}</span>
</CategorySmallItemRenderer>
))}
</HorizontalScrollView>
</motion.div>
<VerticalScrollView className="mt-5! overflow-y-auto h-full">
{filteredReceiptItems.map((food) => (
<MenuItemRenderer key={food.id}>
<MenuItem food={food} />
</MenuItemRenderer>
))}
</VerticalScrollView>
</section>
<AnimatedBottomSheet title="فیلتر ها" visible={filterModal} outDelay={150} onClick={toggleFilterModal}>
<div className="ps-8.5 pe-[31px] mt-[89px]">
<ComboBox
title="محتویات"
options={contents}
selectedId={selectedContentId}
onSelectionChange={changeSelectedContent} />
<ComboBox
className="relative mt-9.5"
title="روش ارسال"
options={shippings}
selectedId={selectedShippingId}
onSelectionChange={changeSelectedShipping} />
<div
className="flex w-full mt-[23px] h-11 items-center justify-between gap-2 px-3 py-4 relative bg-white/29 rounded-xl border border-solid border-neutral-200 cursor-pointer focus:outline-none focus:ring-2 focus:ring-black"
>
<span className="inline-flex items-center gap-2.5 text-sm2">
<MedalStar size="16" color="#333333" />
دارای امتیاز پس از خرید
</span>
<Switch className="w-12 h-6" />
</div>
<div
className="flex w-full mt-[23px] h-11 items-center justify-between gap-2 px-3 py-4 relative bg-white/29 rounded-xl border border-solid border-neutral-200 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} />
دارای کد تخفیف
</span>
<Switch />
</div>
</div>
<hr className="text-white/40 mt-12" />
<div className="px-9 pt-6 flex justify-between gap-[22px]">
<div className="w-full">
<Button>اعمال فیلتر</Button>
</div>
<div className="w-full">
<Button className="bg-disabled! text-disabled-text!">حذف فیلتر</Button>
</div>
</div>
</AnimatedBottomSheet>
<AnimatedBottomSheet title="مرتب کردن بر اساس" visible={sortingModal} inDelay={150} onClick={toggleSortingModal}>
<div className="px-8.5 py-10 justify-between">
{sortings.map((v, i) => {
return (
<div key={i}>
<div onClick={() => changeSorting(i)} className="text-sm2 font-medium cursor-pointer">
{v}
</div>
{i < sortings.length - 1 && <hr className="text-white/40 mb-4 mt-4" />}
</div>
)
})}
</div>
</AnimatedBottomSheet>
</div>
);
};
export default MenuIndex;