menu and categories api , add map

This commit is contained in:
Mahdi Rahimi 2026-01-03 14:14:34 +03:30
parent 82d80c2d7b
commit 400a916541
7 changed files with 1390 additions and 68 deletions

1
.gitignore vendored
View File

@ -11,6 +11,7 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
.env
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*

1265
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,18 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@neshan-maps-platform/leaflet": "^1.0.8",
"@reduxjs/toolkit": "^2.11.2", "@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.1.14", "@tailwindcss/vite": "^4.1.14",
"axios": "^1.13.2", "axios": "^1.13.2",
"framer-motion": "^12.23.26", "framer-motion": "^12.23.26",
"leaflet": "^1.9.4",
"mapbox-gl": "^3.17.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-leaflet": "^5.0.0",
"react-map-gl": "^8.1.0",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-router-dom": "^7.9.4", "react-router-dom": "^7.9.4",
"recharts": "^3.6.0", "recharts": "^3.6.0",

View File

@ -35,6 +35,8 @@ import coffee05 from "../../assets/image/coffee05.png";
import coffee06 from "../../assets/image/coffee06.png"; import coffee06 from "../../assets/image/coffee06.png";
import defaultuser from "../../assets/image/defaultuser.jpg"; import defaultuser from "../../assets/image/defaultuser.jpg";
import { MapPicker } from "./map/map.jsx";
// Services // Services
import cafeService from "../../services/cafe"; import cafeService from "../../services/cafe";
@ -98,6 +100,7 @@ export default function EditCafe() {
const [newCategory, setNewCategory] = useState(""); const [newCategory, setNewCategory] = useState("");
const [editingIndex, setEditingIndex] = useState(null); const [editingIndex, setEditingIndex] = useState(null);
const [editValue, setEditValue] = useState(""); const [editValue, setEditValue] = useState("");
const [location, setLocation] = useState({lat: 32.64762831857033, lng: 51.71143696482368});
// Effects // Effects
useEffect(() => { useEffect(() => {
@ -170,6 +173,8 @@ export default function EditCafe() {
setEditValue(""); setEditValue("");
}; };
console.log("Cafe location:", location);
// Render States // Render States
if (loading) { if (loading) {
return ( return (
@ -200,7 +205,7 @@ export default function EditCafe() {
return ( return (
<section dir="rtl" className="w-full overflow-x-hidden"> <section dir="rtl" className="w-full overflow-x-hidden">
{editmenu ? ( {editmenu ? (
<EditCafeMenu setEditMenu={setEditMenu} /> <EditCafeMenu setEditMenu={setEditMenu} id={id} />
) : ( ) : (
<> <>
<style>{` <style>{`
@ -475,6 +480,13 @@ export default function EditCafe() {
<p>29 دیدگاه</p>{" "} <p>29 دیدگاه</p>{" "}
<p className="font-bold text-text1">مشاهده بیشتر</p>{" "} <p className="font-bold text-text1">مشاهده بیشتر</p>{" "}
</div> </div>
<MapPicker
value={location}
onChange={setLocation}
className="h-[400px] w-full rounded-xl mt-10"
/>
</div> </div>
</div> </div>
</> </>

View File

@ -1,44 +1,75 @@
import { useState } from "react"; import { useState, useEffect } from "react";
import { CgAddR } from "react-icons/cg"; import { CgAddR } from "react-icons/cg";
import { PiCoffee } from "react-icons/pi"; import { PiCoffee } from "react-icons/pi";
import { FaRegEdit } from "react-icons/fa"; import { FaRegEdit } from "react-icons/fa";
import CoffeeCard from "./Component/CoffeeCard"; import CoffeeCard from "./Component/CoffeeCard";
import coffee12 from "../../assets/image/coffee12.png"; import coffee12 from "../../assets/image/coffee12.png";
import cafeService from "../../services/cafe";
const DEFAULT_CATEGORIES = [
"نوشیدنی سرد",
"نوشیدنی گرم",
"کیک و دسر",
"صبحانه",
"ساندویچ و برگر",
"سالاد و پیش غذا",
];
const PRODUCTS = [
{
id: 1,
title: "اسپرسو",
description: "قهوه 100٪ عربیکا با عطر بالا",
price: "118,000",
image: coffee12,
},
{
id: 2,
title: "لاته",
description: "شیر بخار داده شده + اسپرسو",
price: "135,000",
image: coffee12,
},
{
id: 3,
title: "کاپوچینو",
description: "کلاسیک ایتالیایی با فوم شیر",
price: "142,000",
image: coffee12,
},
];
export default function EditCafeMenu({ setEditMenu }) {
export default function EditCafeMenu({ setEditMenu, id }) {
const [selectedCategory, setSelectedCategory] = useState(null); const [selectedCategory, setSelectedCategory] = useState(null);
const [selectedCatId, setSelectedCatId] = useState(null);
const [categories, setCategories] = useState([]);
const [items, setItems] = useState([]);
const getMenuItems = async () => {
const params = { cafeId: id };
try {
const response = await cafeService.getMenuItems(params);
const data = response.data?.data?.docs || [];
////find categories////
const categoryList = data.map(item => item?.category);
const uniqueCategories = Array.from(
new Map(categoryList.map(item => [item?._id, item])).values()
);
setCategories(uniqueCategories);
// console.log("Categories:", uniqueCategories);
////find categories////
} catch (error) {
console.error("Error fetching menu items:", error);
}
};
// فقط یک بار در mount
useEffect(() => {
getMenuItems();
}, [id]);
// وقتی categories تغییر میکند، default category رو set کن
useEffect(() => {
if (categories.length > 0) {
const defaultCategory = categories[0] || categories[0] || null;
setSelectedCatId(defaultCategory?._id);
setSelectedCategory(defaultCategory);
}
}, [categories]);
const getItemsByCategory = async (categoryId) => {
const params = {
cafeId: id,
categoryInput: selectedCategory?._id
};
try {
const response = await cafeService.getItemsByCategory(params);
setItems(response.data?.data?.docs || []);
} catch (error) {
console.error("Error fetching items by category:", error);
}
};
// وقتی selectedCategory تغییر میکند، items رو fetch کن
useEffect(() => {
if (selectedCategory) {
getItemsByCategory();
}
}, [selectedCategory]);
return ( return (
<div> <div>
@ -70,20 +101,19 @@ export default function EditCafeMenu({ setEditMenu }) {
</div> </div>
<div className="flex mr-6 gap-4 relative"> <div className="flex mr-6 gap-4 relative">
{DEFAULT_CATEGORIES.map((cat, index) => ( {categories.map((cat, index) => (
<button <button
key={index} key={index}
onClick={() => setSelectedCategory(cat)} onClick={() => { setSelectedCategory(cat), setSelectedCatId(cat._id) }}
className={`relative -bottom-[2px] pb-2 text-sm md:text-base transition-all className={`relative -bottom-[2px] pb-2 text-sm md:text-base transition-all
${ ${selectedCatId === cat?._id
selectedCategory === cat ? "text-text1 font-bold"
? "text-text1 font-bold" : "text-gray-400 hover:text-text1"
: "text-gray-400 hover:text-text1" }`}
}`}
> >
{cat} {cat?.name}
{selectedCategory === cat && ( {selectedCatId === cat?._id && (
<span <span
className="absolute left-0 -bottom-[9px] w-full h-[5px] className="absolute left-0 -bottom-[9px] w-full h-[5px]
bg-border rounded-full transition-all duration-300" bg-border rounded-full transition-all duration-300"
@ -100,16 +130,17 @@ export default function EditCafeMenu({ setEditMenu }) {
<CgAddR /> <CgAddR />
افزودن زیر عنوان افزودن زیر عنوان
</div> </div>
<div className="flex font-bold items-center text-center gap-2 mt-4"> <div className="flex font-bold items-center text-center gap-2 my-4">
<FaRegEdit size={30} /> <FaRegEdit size={30} />
<PiCoffee size={30} /> <PiCoffee size={30} />
<p className="border-b-2 border-border">قهوه ها</p> <p className="border-b-2 border-border">{selectedCategory?.name}</p>
</div> </div>
<section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <section className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{PRODUCTS.map((item) => ( {items.map((item) => (
<CoffeeCard <CoffeeCard
key={item.id} key={item?._id}
title={item.title} title={item?.title}
description={item.description} description={item.description}
price={item.price} price={item.price}
image={item.image} image={item.image}

View File

@ -0,0 +1,39 @@
import L from '@neshan-maps-platform/leaflet';
import '@neshan-maps-platform/leaflet/dist/leaflet.css';
import { useEffect } from 'react';
export const MapPicker = ({ value, onChange, className }) => {
useEffect(() => {
const map = new L.Map("map", {
key: import.meta.env.VITE_NESHAN_API_KEY,
maptype: "neshan",
poi: false,
traffic: false,
center: value
// || [32.64762831857033, 51.71143696482368]
,
zoom: 14,
});
let marker;
if (value) {
marker = L.marker(value).addTo(map);
}
map.on('click', function(e) {
const { lat, lng } = e.latlng;
if (marker) {
map.removeLayer(marker);
}
marker = L.marker([lat, lng]).addTo(map);
onChange([lat, lng]);
});
return () => {
map.remove();
};
}, []);
return (
<div id="map" className={`${className}`}></div>
);
}

View File

@ -5,9 +5,16 @@ const cafeService = {
getCafeById: (id) => requests.get(`/cafe/v1/get-cafe-profile-by-cafe/${id}`), getCafeById: (id) => requests.get(`/cafe/v1/get-cafe-profile-by-cafe/${id}`),
editCafe: (cafeData) => requests.put(`/cafe/v1/edit-cafe-profile-by-admin`, cafeData), editCafe: (cafeData) => requests.put(`/cafe/v1/edit-cafe-profile-by-admin`, cafeData),
editMenu: (menuData) => requests.put(`/cafe/v1/edit-menu-items`, menuData),
getMenuItems: (params) => requests.getByParams(`/cafemenu/get-menu-category-items`, params),
getItemsByCategory: (params) => requests.getByParams(`/cafemenu/get-menu-items`, params),
addMenuItem: (itemData) => requests.post(`/cafemenu/create-cafe-menu`, itemData), addMenuItem: (itemData) => requests.post(`/cafemenu/create-cafe-menu`, itemData),
editMenu: (menuData) => requests.put(`/cafe/v1/edit-menu-items`, menuData),
addCategory: (categoryData) => requests.post(`/cafemenu/add-category`, categoryData),
}; };
export default cafeService; export default cafeService;