fix layout & responsive everything
This commit is contained in:
parent
5b494855c8
commit
c2ef1976e3
|
|
@ -2883,9 +2883,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -3892,10 +3892,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tar": {
|
"node_modules/tar": {
|
||||||
"version": "7.5.1",
|
"version": "7.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz",
|
||||||
"integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==",
|
"integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==",
|
||||||
"license": "ISC",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/fs-minipass": "^4.0.0",
|
"@isaacs/fs-minipass": "^4.0.0",
|
||||||
"chownr": "^3.0.0",
|
"chownr": "^3.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,47 @@
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
|
||||||
|
// Redux
|
||||||
import { hideToast } from "../redux/slices/toastSlice";
|
import { hideToast } from "../redux/slices/toastSlice";
|
||||||
import { motion, AnimatePresence } from "framer-motion"; //eslint-disable-line no-unused-vars
|
|
||||||
|
// Animation Variants
|
||||||
|
const ANIMATION_VARIANTS = {
|
||||||
|
mobile: {
|
||||||
|
initial: { y: 100, opacity: 0 },
|
||||||
|
animate: { y: 0, opacity: 1 },
|
||||||
|
exit: { y: 100, opacity: 0 },
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
initial: { x: -100, opacity: 0 },
|
||||||
|
animate: { x: 0, opacity: 1 },
|
||||||
|
exit: { x: -100, opacity: 0 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const TOAST_COLORS = {
|
||||||
|
success: "bg-green-500",
|
||||||
|
error: "bg-red-500",
|
||||||
|
info: "bg-blue-500",
|
||||||
|
};
|
||||||
|
|
||||||
|
const TOAST_DURATION = 3000;
|
||||||
|
|
||||||
export default function Toast() {
|
export default function Toast() {
|
||||||
const { message, type, visible } = useSelector((state) => state.toast);
|
const { message, type, visible } = useSelector((state) => state.toast);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
|
||||||
|
|
||||||
|
// Auto-hide toast after duration
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
const timer = setTimeout(() => dispatch(hideToast()), 3000);
|
const timer = setTimeout(() => dispatch(hideToast()), TOAST_DURATION);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
}, [visible, dispatch]);
|
}, [visible, dispatch]);
|
||||||
|
|
||||||
const bgColor =
|
const bgColor = TOAST_COLORS[type] || TOAST_COLORS.info;
|
||||||
type === "success"
|
const variants = isMobile ? ANIMATION_VARIANTS.mobile : ANIMATION_VARIANTS.desktop;
|
||||||
? "bg-green-500"
|
|
||||||
: type === "error"
|
|
||||||
? "bg-red-500"
|
|
||||||
: "bg-blue-500";
|
|
||||||
|
|
||||||
// موبایل: از پایین بیا و برگرد پایین
|
|
||||||
const mobileVariants = {
|
|
||||||
initial: { y: 100, opacity: 0 },
|
|
||||||
animate: { y: 0, opacity: 1 },
|
|
||||||
exit: { y: 100, opacity: 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
// دسکتاپ: از چپ بیا
|
|
||||||
const desktopVariants = {
|
|
||||||
initial: { x: -100, opacity: 0 },
|
|
||||||
animate: { x: 0, opacity: 1 },
|
|
||||||
exit: { x: -100, opacity: 0 }
|
|
||||||
};
|
|
||||||
|
|
||||||
// تعیین حالت بر اساس اندازه صفحه
|
|
||||||
const isMobile = typeof window !== "undefined" && window.innerWidth < 768;
|
|
||||||
const variants = isMobile ? mobileVariants : desktopVariants;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
|
@ -51,11 +55,15 @@ export default function Toast() {
|
||||||
type: "spring",
|
type: "spring",
|
||||||
stiffness: 260,
|
stiffness: 260,
|
||||||
damping: 20,
|
damping: 20,
|
||||||
duration: 0.9
|
|
||||||
}}
|
}}
|
||||||
className={`${bgColor} fixed left-1/2 md:left-6 bottom-20 md:bottom-auto md:top-20 text-white px-6 py-3 rounded-lg shadow-2xl z-60 transform -translate-x-1/2 md:translate-x-0 max-w-xs md:max-w-sm`}
|
className={`${bgColor} fixed z-50 text-white px-6 py-3 rounded-lg shadow-2xl max-w-xs md:max-w-sm ${
|
||||||
|
isMobile
|
||||||
|
? "left-1/2 bottom-6 -translate-x-1/2"
|
||||||
|
: "top-6 left-6"
|
||||||
|
}`}
|
||||||
|
role="alert"
|
||||||
>
|
>
|
||||||
{message}
|
<p className="text-sm md:text-base font-medium">{message}</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,15 @@ import Pic from "../../../assets/icons/pic.png";
|
||||||
|
|
||||||
const Header = ({ title }) => {
|
const Header = ({ title }) => {
|
||||||
return (
|
return (
|
||||||
<header className="flex items-center justify-between gap-4 p-4 ml-5 mr-2">
|
<header className="flex items-center justify-end gap-4 p-3 ml-5 mr-2">
|
||||||
<h3 className="text-[#402E32] font-bold text-lg">{title}</h3>
|
{/* <h3 className="text-[#402E32] font-bold text-lg">{title}</h3> */}
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-12 h-10 border-2 border-[#8B8886] flex justify-center items-center rounded-2xl bg-[#F4EADF]">
|
<div className="w-12 h-10 border-2 border-[#8B8886] flex justify-center items-center rounded-2xl bg-[#F4EADF]">
|
||||||
<img src={Search} alt="search" />
|
<img src={Search} alt="search" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between border-2 border-[#8B8886] rounded-2xl px-3 w-60 py-1 bg-[#F4EADF]">
|
<div className="flex items-center w-40 md:w-60 justify-between border-2 border-[#8B8886] rounded-2xl px-3 py-1 bg-[#F4EADF]">
|
||||||
<img src={Pic} className="w-8 h-8 rounded-full ml-1" alt="user" />
|
<img src={Pic} className="w-8 h-8 rounded-full ml-1" alt="user" />
|
||||||
<p className="text-[#402E32] text-sm">سارا راد</p>
|
<p className="text-[#402E32] text-sm">سارا راد</p>
|
||||||
<img src={Arrow} className="w-4 h-4 ml-1" alt="arrow" />
|
<img src={Arrow} className="w-4 h-4 ml-1" alt="arrow" />
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import React, { useState } from "react";
|
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import LogoDM from "../../../assets/icons/LogoDM.svg";
|
import LogoDM from "../../../assets/icons/LogoDM.svg";
|
||||||
import { BiBarChartAlt2 } from "react-icons/bi";
|
import { BiBarChartAlt2 } from "react-icons/bi";
|
||||||
|
|
@ -7,10 +6,9 @@ import { PiCoffee } from "react-icons/pi";
|
||||||
import { HiOutlineLogout } from "react-icons/hi";
|
import { HiOutlineLogout } from "react-icons/hi";
|
||||||
import { FiMenu, FiX } from "react-icons/fi";
|
import { FiMenu, FiX } from "react-icons/fi";
|
||||||
|
|
||||||
const Sidebar = () => {
|
export default function Sidebar({ className, isOpen, setIsOpen }) {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
const isActive = (path) => location.pathname === path;
|
const isActive = (path) => location.pathname === path;
|
||||||
|
|
||||||
|
|
@ -37,7 +35,7 @@ const Sidebar = () => {
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsOpen(!isOpen)}
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
className="sm:hidden fixed top-3 right-3 z-[70] bg-[#7F4629] text-white p-2.5 rounded-lg shadow-lg"
|
className="lg:hidden fixed top-4 right-4 z-[70] bg-[#7F4629] text-white p-2.5 rounded-lg shadow-lg"
|
||||||
aria-label="Toggle menu"
|
aria-label="Toggle menu"
|
||||||
>
|
>
|
||||||
{isOpen ? <FiX className="w-5 h-5" /> : <FiMenu className="w-5 h-5" />}
|
{isOpen ? <FiX className="w-5 h-5" /> : <FiMenu className="w-5 h-5" />}
|
||||||
|
|
@ -45,27 +43,21 @@ const Sidebar = () => {
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div
|
<div
|
||||||
className="sm:hidden fixed inset-0 bg-black/50 z-[60]"
|
className="lg:hidden fixed inset-0 bg-black/50 z-[60]"
|
||||||
onClick={closeSidebar}
|
onClick={closeSidebar}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<aside
|
<aside
|
||||||
dir="rtl"
|
dir="rtl"
|
||||||
className={`
|
className={`${className}
|
||||||
fixed top-0 right-0 z-[65]
|
|
||||||
h-screen w-[260px]
|
|
||||||
flex flex-col
|
|
||||||
bg-[#EFEEEE]
|
|
||||||
rounded-tl-2xl rounded-bl-2xl
|
|
||||||
shadow-xl border-l border-[#D9CAB3]
|
|
||||||
transition-transform duration-300 ease-in-out
|
transition-transform duration-300 ease-in-out
|
||||||
${isOpen ? 'translate-x-0' : 'translate-x-full'}
|
${isOpen ? 'translate-x-0' : 'translate-x-full'}
|
||||||
sm:translate-x-0 sm:w-[220px]
|
lg:translate-x-0 lg:w-[220px]
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="flex justify-center mt-5 mb-2">
|
<div className="flex justify-center mt-5 mb-2">
|
||||||
<img src={LogoDM} className="h-10 w-10 sm:h-12 sm:w-12" alt="Logo" />
|
<img src={LogoDM} className="h-10 w-10 lg:h-12 lg:w-12" alt="Logo" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="flex-1 px-3 mt-6 space-y-3 overflow-y-auto">
|
<nav className="flex-1 px-3 mt-6 space-y-3 overflow-y-auto">
|
||||||
|
|
@ -169,5 +161,3 @@ const Sidebar = () => {
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Sidebar;
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
import { Outlet, useLocation } from "react-router-dom";
|
import { Outlet, useLocation } from "react-router-dom";
|
||||||
import Sidebar from "./Sidebar/sidebar";
|
import Sidebar from "./Sidebar/sidebar";
|
||||||
import Header from "./Header/header";
|
import Header from "./Header/header";
|
||||||
|
|
||||||
export default function Layout() {
|
const HEADER_PATHS = [
|
||||||
const location = useLocation();
|
|
||||||
const path = location.pathname;
|
|
||||||
|
|
||||||
// لیست الگوهایی که باید هدر نشون داده بشه
|
|
||||||
const headerPaths = [
|
|
||||||
"/management",
|
"/management",
|
||||||
"/management/",
|
"/management/",
|
||||||
"/management/cafes",
|
"/management/cafes",
|
||||||
|
|
@ -16,27 +12,38 @@ export default function Layout() {
|
||||||
"/cafe",
|
"/cafe",
|
||||||
"/cafes",
|
"/cafes",
|
||||||
"/admin",
|
"/admin",
|
||||||
];
|
];
|
||||||
|
|
||||||
// بررسی: اگر هرکدوم از الگوها داخل path بود، هدر نمایش داده میشه
|
export default function Layout() {
|
||||||
const showHeader = headerPaths.some(
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
(p) => path.startsWith(p) || path.includes(p)
|
const location = useLocation();
|
||||||
);
|
const path = location.pathname;
|
||||||
|
|
||||||
|
// Check if current path should show header
|
||||||
|
const showHeader = HEADER_PATHS.some((p) => path.startsWith(p) || path.includes(p));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-row min-h-screen bg-[#FDF8F4]">
|
<div className="flex min-h-screen w-full bg-[#FDF8F4]">
|
||||||
{/* Sidebar - در موبایل overlay، در دسکتاپ ثابت */}
|
{/* Sidebar */}
|
||||||
<Sidebar />
|
<Sidebar
|
||||||
|
className="fixed top-0 right-0 bottom-0 z-[65] w-[260px] flex flex-col bg-[#EFEEEE] rounded-l-2xl shadow-xl border-l border-[#D9CAB3]"
|
||||||
|
isOpen={isOpen}
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<div className="flex-1 flex flex-col min-h-screen w-full lg:mr-[220px]">
|
<div className="flex-1 flex flex-col min-h-screen w-full lg:mr-[220px]">
|
||||||
|
{/* Header */}
|
||||||
{showHeader && (
|
{showHeader && (
|
||||||
<div className="fixed top-0 right-0 left-0 lg:right-[220px] z-30 bg-[#FDF8F4]">
|
<div className="fixed top-0 right-0 left-0 lg:right-[220px] z-30 bg-[#FDF8F4] border-b border-[#e6e2de]">
|
||||||
<Header title="مدیریت کافهها" />
|
<Header title="مدیریت کافهها" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<main className={`flex-1 w-full overflow-x-hidden p-4 md:p-6 bg-[#FDF8F4] ${showHeader ? 'pt-20 lg:pt-24' : 'pt-16 lg:pt-6'}`}>
|
{/* Main Content Area */}
|
||||||
|
<main
|
||||||
|
className={`flex-1 w-full overflow-x-hidden bg-[#FDF8F4] transition-all p-3 lg:p-6`}
|
||||||
|
>
|
||||||
<div className="max-w-full mx-auto">
|
<div className="max-w-full mx-auto">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ const CafeManagement = () => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
// دریافت لیست کافهها از API
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCafes = async () => {
|
const fetchCafes = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -22,19 +21,16 @@ const CafeManagement = () => {
|
||||||
const res = await cafeService.getCafeList();
|
const res = await cafeService.getCafeList();
|
||||||
if (res.data.success && res.data.data) {
|
if (res.data.success && res.data.data) {
|
||||||
setCafes(res.data.data);
|
setCafes(res.data.data);
|
||||||
|
console.log("✅ Cafes loaded successfully:", res.data);
|
||||||
} else {
|
} else {
|
||||||
setError("دادههای دریافتی معتبر نیست");
|
setError("دادههای دریافتی معتبر نیست");
|
||||||
}
|
}
|
||||||
console.log("✅ Cafes loaded successfully:", res.data);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response) {
|
|
||||||
setError(error.response.data.message || "خطا در دریافت لیست کافهها");
|
|
||||||
} else if (error.request) {
|
|
||||||
setError("خطا در برقراری ارتباط با سرور");
|
|
||||||
} else {
|
|
||||||
setError("خطای نامشخص رخ داده است");
|
|
||||||
}
|
|
||||||
console.error("❌ Error loading cafes:", error);
|
console.error("❌ Error loading cafes:", error);
|
||||||
|
const errorMessage =
|
||||||
|
error.response?.data?.message ||
|
||||||
|
(error.request ? "خطا در برقراری ارتباط با سرور" : "خطای نامشخص رخ داده است");
|
||||||
|
setError(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -43,119 +39,121 @@ const CafeManagement = () => {
|
||||||
fetchCafes();
|
fetchCafes();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<section dir="rtl" className="-mt-12 w-full max-w-full overflow-x-hidden">
|
<div className="flex justify-center items-center h-screen">
|
||||||
{/* دکمه افزودن */}
|
<div className="text-[#7F4629] text-xl font-bold">در حال بارگذاری...</div>
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<button className="w-45 h-12 bg-[#7F4629] text-white mt-8 rounded-3xl flex items-center justify-center gap-3 hover:bg-amber-950 transition-all cursor-pointer">
|
|
||||||
<p className="-mt-0.5">افزودن شعبه جدید</p>
|
|
||||||
<img src={Vector9} alt="لوگو" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{/* عنوان */}
|
if (error) {
|
||||||
<h3 className="text-[#402E32] w-[90%] font-bold mt-10">کافه های شما</h3>
|
return (
|
||||||
|
|
||||||
{/* نمایش خطا */}
|
|
||||||
{error && (
|
|
||||||
<div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl mt-4">
|
<div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl mt-4">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{/* نمایش لودینگ */}
|
if (cafes.length === 0) {
|
||||||
{loading ? (
|
return (
|
||||||
<div className="flex justify-center items-center mt-20">
|
<div className="text-center mt-20 text-gray-500">
|
||||||
<div className="text-[#7F4629] text-xl font-bold">در حال بارگذاری...</div>
|
هیچ کافهای یافت نشد
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{/* هدر جدول */}
|
|
||||||
<div className="mt-10 hidden md:grid grid-cols-6 gap-x-42 bg-[#EFEEEE] px-4 py-3 rounded-xl text-[#402E32] font-medium">
|
|
||||||
<h3>لوگو</h3>
|
|
||||||
<h3>اسم</h3>
|
|
||||||
<h3>آدرس</h3>
|
|
||||||
<h3>ریتینگ</h3>
|
|
||||||
<h3>ساعت کاری</h3>
|
|
||||||
<h3>ادیت</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{/* ردیفهای جدول - دسکتاپ */}
|
return (
|
||||||
{cafes.length > 0 ? (
|
|
||||||
<>
|
<>
|
||||||
{/* نمای دسکتاپ */}
|
{/* جدول دسکتاپ */}
|
||||||
<div className="hidden md:grid grid-cols-6 gap-y-9 gap-x-33 mt-10 text-[#402E32] font-medium pl-4">
|
<div className="hidden lg:block mt-10 overflow-x-auto">
|
||||||
|
<table className="w-full border-collapse">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-[#EFEEEE]">
|
||||||
|
<th className="px-4 py-3 text-right text-[#402E32] font-medium text-sm">لوگو</th>
|
||||||
|
<th className="px-4 py-3 text-right text-[#402E32] font-medium text-sm">اسم</th>
|
||||||
|
<th className="px-4 py-3 text-right text-[#402E32] font-medium text-sm">آدرس</th>
|
||||||
|
<th className="px-4 py-3 text-right text-[#402E32] font-medium text-sm">ریتینگ</th>
|
||||||
|
<th className="px-4 py-3 text-right text-[#402E32] font-medium text-sm">ساعت کاری</th>
|
||||||
|
<th className="px-4 py-3 text-right text-[#402E32] font-medium text-sm">ادیت</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
{cafes.map((cafe) => (
|
{cafes.map((cafe) => (
|
||||||
<React.Fragment key={cafe._id}>
|
<tr key={cafe._id} className="border-b border-[#EFEEEE] hover:bg-gray-50 transition-colors">
|
||||||
{/* لوگو */}
|
<td className="px-4 py-4 text-right">
|
||||||
<img
|
<img
|
||||||
src={cafe.photo || Pic1}
|
src={cafe.photo || Pic1}
|
||||||
|
alt={cafe.Name}
|
||||||
className="w-10 h-10 rounded-full object-cover"
|
className="w-10 h-10 rounded-full object-cover"
|
||||||
alt="لوگو"
|
|
||||||
/>
|
/>
|
||||||
|
</td>
|
||||||
{/* نام */}
|
<td className="px-4 py-4 text-right text-[#402E32] font-medium text-sm whitespace-nowrap">
|
||||||
<h3 className="whitespace-nowrap -mr-2">{cafe.Name}</h3>
|
{cafe.Name}
|
||||||
|
</td>
|
||||||
{/* آدرس */}
|
<td className="px-4 py-4 text-right text-[#402E32] text-sm max-w-xs overflow-hidden text-ellipsis">
|
||||||
<h3 className="whitespace-nowrap -mr-7 overflow-hidden text-ellipsis">
|
|
||||||
{cafe.address}
|
{cafe.address}
|
||||||
</h3>
|
</td>
|
||||||
|
<td className="px-4 py-4 text-right">
|
||||||
{/* ریتینگ */}
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex gap-1 mr-5">
|
<img src={Star1} alt="rating" className="w-5 h-5" />
|
||||||
<img className="w-6 h-6" src={Star1} alt="ستاره" />
|
<span className="text-[#402E32] text-sm">{cafe.rating || 0}</span>
|
||||||
<h3>{cafe.rating || 0}</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
</td>
|
||||||
{/* ساعت کاری */}
|
<td className="px-4 py-4 text-right">
|
||||||
<div className="flex gap-1 whitespace-nowrap">
|
<div className="flex items-center gap-2">
|
||||||
<img className="w-5 h-5" src={Group} alt="لوگو" />
|
<img src={Group} alt="time" className="w-5 h-5" />
|
||||||
<h3 className="-mt-1">{cafe.openinghour || "نامشخص"}</h3>
|
<span className="text-[#402E32] text-sm whitespace-nowrap">
|
||||||
|
{cafe.openinghour || "نامشخص"}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</td>
|
||||||
{/* دکمه ادیت */}
|
<td className="px-4 py-4 text-center">
|
||||||
<Link
|
<Link
|
||||||
to={`/edit-cafe/${cafe._id}`}
|
to={`/edit-cafe/${cafe._id}`}
|
||||||
className="flex justify-center whitespace-nowrap gap-2 border-2 border-[#BB8F70] px-10 py-3 rounded-3xl font-light -mt-1.5 -mx-4 hover:bg-[#7F4629] hover:text-white hover:border-none cursor-pointer transition-all duration-500"
|
className="inline-flex items-center justify-center gap-2 border-2 border-[#BB8F70] px-6 py-2 rounded-3xl text-sm font-light text-[#402E32] hover:bg-[#7F4629] hover:text-white hover:border-[#7F4629] transition-all duration-300"
|
||||||
>
|
>
|
||||||
<h3>ادیت کافه</h3>
|
<span>ادیت</span>
|
||||||
<BiEdit className="w-4.5 h-4.5 mt-1.5" />
|
<BiEdit className="w-4 h-4" />
|
||||||
</Link>
|
</Link>
|
||||||
</React.Fragment>
|
</td>
|
||||||
|
</tr>
|
||||||
))}
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* نمای موبایل - کارتها */}
|
{/* کارت موبایل */}
|
||||||
<div className="md:hidden mt-6 space-y-4">
|
<div className="lg:hidden mt-6 space-y-4">
|
||||||
{cafes.map((cafe) => (
|
{cafes.map((cafe) => (
|
||||||
<div key={cafe._id} className="bg-white border-2 border-[#8b8886] rounded-xl p-4">
|
<div key={cafe._id} className="bg-white border-2 border-[#8b8886] rounded-xl p-4">
|
||||||
<div className="flex items-start gap-4 mb-4">
|
<div className="flex items-start gap-4 mb-4">
|
||||||
<img
|
<img
|
||||||
src={cafe.photo || Pic1}
|
src={cafe.photo || Pic1}
|
||||||
|
alt={cafe.Name}
|
||||||
className="w-16 h-16 rounded-full object-cover flex-shrink-0"
|
className="w-16 h-16 rounded-full object-cover flex-shrink-0"
|
||||||
alt="لوگو"
|
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1 min-w-0">
|
||||||
<h3 className="font-bold text-[#402E32] mb-1">{cafe.Name}</h3>
|
<h3 className="font-bold text-[#402E32] mb-1 text-sm">{cafe.Name}</h3>
|
||||||
<p className="text-sm text-gray-600">{cafe.address}</p>
|
<p className="text-xs text-gray-600 truncate">{cafe.address}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-between mb-3 text-sm">
|
<div className="flex items-center justify-between mb-4 text-xs gap-4">
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<img className="w-5 h-5" src={Star1} alt="ستاره" />
|
<img src={Star1} alt="rating" className="w-4 h-4" />
|
||||||
<span>{cafe.rating || 0}</span>
|
<span className="text-[#402E32]">{cafe.rating || 0}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<img className="w-4 h-4" src={Group} alt="لوگو" />
|
<img src={Group} alt="time" className="w-4 h-4" />
|
||||||
<span>{cafe.openinghour || "نامشخص"}</span>
|
<span className="text-[#402E32]">{cafe.openinghour || "نامشخص"}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={`/edit-cafe/${cafe._id}`}
|
to={`/edit-cafe/${cafe._id}`}
|
||||||
className="flex justify-center items-center gap-2 w-full border-2 border-[#BB8F70] py-2 rounded-full font-medium hover:bg-[#7F4629] hover:text-white hover:border-[#7F4629] transition-all duration-300"
|
className="flex justify-center items-center gap-2 w-full border-2 border-[#BB8F70] py-2 rounded-full text-sm font-medium text-[#402E32] hover:bg-[#7F4629] hover:text-white hover:border-[#7F4629] transition-all duration-300"
|
||||||
>
|
>
|
||||||
<span>ادیت کافه</span>
|
<span>ادیت کافه</span>
|
||||||
<BiEdit className="w-4 h-4" />
|
<BiEdit className="w-4 h-4" />
|
||||||
|
|
@ -164,13 +162,24 @@ const CafeManagement = () => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
);
|
||||||
<div className="text-center mt-20 text-gray-500">
|
};
|
||||||
هیچ کافهای یافت نشد
|
|
||||||
|
return (
|
||||||
|
<section dir="rtl" className="w-full pt-24 max-w-full overflow-x-hidden">
|
||||||
|
{/* بخش دکمه اضافه کردن */}
|
||||||
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
<button className="flex items-center justify-center gap-3 px-6 py-3 bg-[#7F4629] text-white rounded-3xl text-sm lg:text-base font-medium hover:bg-amber-950 transition-all duration-300 cursor-pointer">
|
||||||
|
<span>افزودن شعبه جدید</span>
|
||||||
|
<img src={Vector9} alt="افزودن" className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</>
|
{/* عنوان */}
|
||||||
)}
|
<h1 className="text-[#402E32] font-bold text-lg lg:text-xl mb-6">کافه های شما</h1>
|
||||||
|
|
||||||
|
{/* محتوای اصلی */}
|
||||||
|
{renderContent()}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,103 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { useParams, useNavigate } from "react-router-dom";
|
import { useParams, useNavigate } from "react-router-dom";
|
||||||
import Bg1 from "../../assets/icons/bg1.svg";
|
|
||||||
import { GrLocation } from "react-icons/gr";
|
import { GrLocation } from "react-icons/gr";
|
||||||
import { BiEdit } from "react-icons/bi";
|
import { BiEdit } from "react-icons/bi";
|
||||||
import { FaRegStar } from "react-icons/fa";
|
import { FaRegStar } from "react-icons/fa";
|
||||||
|
import { IoMdCheckmark, IoMdClose } from "react-icons/io";
|
||||||
|
|
||||||
|
// Assets
|
||||||
|
import Bg1 from "../../assets/icons/bg1.svg";
|
||||||
|
import Vector9 from "../../assets/icons/Vector9.svg";
|
||||||
import Vector11 from "../../assets/icons/Vector11.svg";
|
import Vector11 from "../../assets/icons/Vector11.svg";
|
||||||
import Vector12 from "../../assets/icons/Vector12.svg";
|
import Vector12 from "../../assets/icons/Vector12.svg";
|
||||||
import Vector13 from "../../assets/icons/Vector13.svg";
|
import Vector13 from "../../assets/icons/Vector13.svg";
|
||||||
import Vector14 from "../../assets/icons/Vector14.svg";
|
import Vector14 from "../../assets/icons/Vector14.svg";
|
||||||
import Vector15 from "../../assets/icons/Vector15.svg";
|
import Vector15 from "../../assets/icons/Vector15.svg";
|
||||||
import Vector9 from "../../assets/icons/Vector9.svg";
|
|
||||||
import Vector16 from "../../assets/icons/Vector16.svg";
|
import Vector16 from "../../assets/icons/Vector16.svg";
|
||||||
import Sperso from "../../assets/icons/sperso.svg";
|
|
||||||
import Coffee2 from "../../assets/icons/coffee2.svg";
|
|
||||||
import Coffee1 from "../../assets/icons/coffee1.svg";
|
import Coffee1 from "../../assets/icons/coffee1.svg";
|
||||||
|
import Coffee2 from "../../assets/icons/coffee2.svg";
|
||||||
import Coffee3 from "../../assets/icons/coffee3.svg";
|
import Coffee3 from "../../assets/icons/coffee3.svg";
|
||||||
|
import Sperso from "../../assets/icons/sperso.svg";
|
||||||
import Edit from "../../assets/icons/edit.svg";
|
import Edit from "../../assets/icons/edit.svg";
|
||||||
import { IoMdCheckmark, IoMdClose } from "react-icons/io";
|
|
||||||
|
// Services
|
||||||
import cafeService from "../../services/cafe";
|
import cafeService from "../../services/cafe";
|
||||||
|
|
||||||
export default function EditCafe() {
|
// Constants
|
||||||
const { id } = useParams();
|
const DEFAULT_CATEGORIES = [
|
||||||
const navigate = useNavigate();
|
|
||||||
const [cafeData, setCafeData] = useState(null);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
|
|
||||||
const [categories, setCategories] = useState(() => {
|
|
||||||
const saved = localStorage.getItem('cafeCategories');
|
|
||||||
return saved ? JSON.parse(saved) : [
|
|
||||||
"نوشیدنی سرد",
|
"نوشیدنی سرد",
|
||||||
"نوشیدنی گرم",
|
"نوشیدنی گرم",
|
||||||
"کیک و دسر",
|
"کیک و دسر",
|
||||||
"صبحانه",
|
"صبحانه",
|
||||||
"ساندویچ و برگر",
|
"ساندویچ و برگر",
|
||||||
"سالاد و پیش غذا",
|
"سالاد و پیش غذا",
|
||||||
];
|
];
|
||||||
});
|
|
||||||
|
|
||||||
|
const CAFE_FEATURES = [
|
||||||
|
{ icon: Coffee3, label: "منو کافه:", width: "lg:w-[140px]" },
|
||||||
|
{ icon: Vector15, label: "ساعت کاری:", value: "23 - 8" },
|
||||||
|
{ icon: Vector14, label: "رزرو :", value: "رزرو آنلاین" },
|
||||||
|
{ icon: Vector11, label: "موسیقی :", value: "موسیقی زنده آخر هفته" },
|
||||||
|
{ icon: Vector13, label: "پارکینگ :", value: "عمومی" },
|
||||||
|
{ icon: Vector12, label: "دسترسی آسان :", value: "مناسب افراد ناتوان" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const CAFE_PRODUCTS = [
|
||||||
|
{
|
||||||
|
name: "اسپرسو100%",
|
||||||
|
price: "118.000",
|
||||||
|
image: Sperso,
|
||||||
|
description: "45 میلی لیتر، قهوه، 100% عربیکا، دم شده با دستگاه اسپرسو ساز، به همراه یک عدد آب معدنی مینی",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "کارامل ماکیاتو",
|
||||||
|
price: "149.000",
|
||||||
|
image: Coffee1,
|
||||||
|
description: "220 میلی لیتر، 2 شات اسپرسو 30% روبوستا، 70% عربیکا، یک لکه فوم شیر، سیروپ کارامل",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "اسپرسو آفوگاتو",
|
||||||
|
price: "118.000",
|
||||||
|
image: Coffee2,
|
||||||
|
description: "اسپرسو، یک اسکوپ بستنی وانیلی",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function EditCafe() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// State Management
|
||||||
|
const [cafeData, setCafeData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
const [categories, setCategories] = useState(() => {
|
||||||
|
const saved = localStorage.getItem("cafeCategories");
|
||||||
|
return saved ? JSON.parse(saved) : DEFAULT_CATEGORIES;
|
||||||
|
});
|
||||||
const [isEditMode, setIsEditMode] = useState(false);
|
const [isEditMode, setIsEditMode] = useState(false);
|
||||||
const [isAdding, setIsAdding] = useState(false);
|
const [isAddingCategory, setIsAddingCategory] = useState(false);
|
||||||
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("");
|
||||||
|
|
||||||
// دریافت اطلاعات کافه
|
// Effects
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getCafeData = async () => {
|
fetchCafeData();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
localStorage.setItem("cafeCategories", JSON.stringify(categories));
|
||||||
|
}, [categories]);
|
||||||
|
|
||||||
|
// API Calls
|
||||||
|
const fetchCafeData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError("");
|
setError("");
|
||||||
try {
|
try {
|
||||||
const res = await cafeService.getCafeList();
|
const res = await cafeService.getCafeList();
|
||||||
if (res.data.success && res.data.data) {
|
if (res.data.success && res.data.data) {
|
||||||
const cafe = res.data.data.find(c => c._id === id);
|
const cafe = res.data.data.find((c) => c._id === id);
|
||||||
if (cafe) {
|
if (cafe) {
|
||||||
setCafeData(cafe);
|
setCafeData(cafe);
|
||||||
console.log("✅ Cafe data loaded:", cafe);
|
console.log("✅ Cafe data loaded:", cafe);
|
||||||
|
|
@ -64,36 +109,23 @@ export default function EditCafe() {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error loading cafe:", error);
|
console.error("❌ Error loading cafe:", error);
|
||||||
if (error.response) {
|
const errorMessage =
|
||||||
setError(error.response.data.message || "خطا در دریافت اطلاعات کافه");
|
error.response?.data?.message ||
|
||||||
} else if (error.request) {
|
(error.request ? "خطا در برقراری ارتباط با سرور" : "خطای نامشخص رخ داده است");
|
||||||
setError("خطا در برقراری ارتباط با سرور");
|
setError(errorMessage);
|
||||||
} else {
|
|
||||||
setError("خطای نامشخص رخ داده است");
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (id) {
|
// Category Handlers
|
||||||
getCafeData();
|
|
||||||
}
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
// ذخیره دستهبندیها در localStorage
|
|
||||||
useEffect(() => {
|
|
||||||
localStorage.setItem('cafeCategories', JSON.stringify(categories));
|
|
||||||
}, [categories]);
|
|
||||||
|
|
||||||
// Handlers
|
|
||||||
const handleAddCategory = () => {
|
const handleAddCategory = () => {
|
||||||
if (newCategory.trim()) {
|
if (newCategory.trim()) {
|
||||||
setCategories([...categories, newCategory.trim()]);
|
setCategories([...categories, newCategory.trim()]);
|
||||||
setNewCategory("");
|
setNewCategory("");
|
||||||
setIsAdding(false);
|
setIsAddingCategory(false);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const scrollContainer = document.querySelector('.categories-scroll');
|
const scrollContainer = document.querySelector(".categories-scroll");
|
||||||
if (scrollContainer) {
|
if (scrollContainer) {
|
||||||
scrollContainer.scrollLeft = scrollContainer.scrollWidth;
|
scrollContainer.scrollLeft = scrollContainer.scrollWidth;
|
||||||
}
|
}
|
||||||
|
|
@ -102,16 +134,15 @@ export default function EditCafe() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteCategory = (index) => {
|
const handleDeleteCategory = (index) => {
|
||||||
const newCategories = categories.filter((_, i) => i !== index);
|
setCategories(categories.filter((_, i) => i !== index));
|
||||||
setCategories(newCategories);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartEdit = (index) => {
|
const handleEditCategory = (index) => {
|
||||||
setEditingIndex(index);
|
setEditingIndex(index);
|
||||||
setEditValue(categories[index]);
|
setEditValue(categories[index]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveEdit = () => {
|
const handleSaveCategory = () => {
|
||||||
if (editValue.trim()) {
|
if (editValue.trim()) {
|
||||||
const newCategories = [...categories];
|
const newCategories = [...categories];
|
||||||
newCategories[editingIndex] = editValue.trim();
|
newCategories[editingIndex] = editValue.trim();
|
||||||
|
|
@ -126,6 +157,7 @@ export default function EditCafe() {
|
||||||
setEditValue("");
|
setEditValue("");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Render States
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-screen">
|
<div className="flex justify-center items-center h-screen">
|
||||||
|
|
@ -153,158 +185,122 @@ export default function EditCafe() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<section dir="rtl" className="w-full overflow-x-hidden">
|
||||||
<style>
|
<style>{`
|
||||||
{`
|
.scrollbar-hide::-webkit-scrollbar { display: none; }
|
||||||
.scrollbar-hide::-webkit-scrollbar {
|
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
|
||||||
display: none;
|
`}</style>
|
||||||
}
|
|
||||||
.scrollbar-hide {
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
body::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
-ms-overflow-style: none;
|
|
||||||
scrollbar-width: none;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
<section dir="rtl" className="lg:-mt-12">
|
|
||||||
<h3 className="text-[#402E32] font-bold mb-4 lg:mb-0">ادیت {cafeData.Name}</h3>
|
|
||||||
|
|
||||||
<div className="mt-4 lg:mt-9 border-2 border-[#8b8886] p-4 lg:p-10 rounded-2xl flex flex-col lg:flex-row gap-4">
|
{/* Header */}
|
||||||
<div className="w-full lg:w-auto">
|
<h1 className="text-[#402E32] font-bold text-lg lg:text-xl mb-6">ادیت {cafeData.Name}</h1>
|
||||||
<img className="w-full lg:w-[461px] rounded-lg" src={cafeData.photo || Bg1} alt="Logo" />
|
|
||||||
|
{/* Main Container */}
|
||||||
|
<div className="border-2 mt-1 border-[#8b8886] p-4 md:p-6 lg:p-10 rounded-2xl">
|
||||||
|
{/* Cafe Info Section */}
|
||||||
|
<div className="flex flex-col lg:flex-row gap-6 mb-8">
|
||||||
|
{/* Image */}
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<img
|
||||||
|
src={cafeData.photo || Bg1}
|
||||||
|
alt={cafeData.Name}
|
||||||
|
className="w-full lg:w-[300px] xl:w-[400px] rounded-lg object-cover"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="relative flex flex-col gap-4 lg:gap-4.5 w-full">
|
{/* Info */}
|
||||||
<div className="flex gap-2 justify-end lg:absolute lg:top-2 lg:left-0 lg:-mt-5">
|
<div className="flex-1">
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="flex gap-3 justify-end mb-6">
|
||||||
<button
|
<button
|
||||||
onClick={() => navigate('/cafe-management')}
|
onClick={() => navigate("/cafe-management")}
|
||||||
className="border-2 border-[#bb8f70] w-20 lg:w-25 h-10 rounded-3xl text-sm lg:text-base text-[#402e32] hover:bg-[#7f4629] hover:text-white transition-all duration-300 hover:border-none">
|
className="px-4 md:px-6 py-2 border-2 border-[#bb8f70] text-[#402e32] rounded-3xl hover:bg-[#7f4629] hover:text-white hover:border-[#7f4629] transition-all duration-300 text-sm md:text-base font-medium"
|
||||||
|
>
|
||||||
انصراف
|
انصراف
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className="px-4 md:px-6 py-2 bg-[#7f4629] text-white rounded-3xl hover:bg-[#5f494f] transition-all duration-300 text-sm md:text-base font-medium">
|
||||||
className="border-2 border-[#bb8f70] bg-[#7f4629] text-white w-20 lg:w-25 h-10 rounded-3xl text-sm lg:text-base hover:bg-[#5f494f] transition-all duration-300 hover:border-none">
|
|
||||||
تایید
|
تایید
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<BiEdit className="w-5 h-5 mt-0 lg:-mt-1" />
|
{/* Basic Info */}
|
||||||
<h3 className="font-bold text-base">{cafeData.Name}</h3>
|
<div className="space-y-4">
|
||||||
<hr className="w-full lg:w-38 -mt-3 border-1 text-[#80931e]" />
|
<div>
|
||||||
|
<h2 className="font-bold text-lg text-[#402E32] mb-2">{cafeData.Name}</h2>
|
||||||
<div className="flex items-center gap-2">
|
<div className="w-16 h-1 bg-[#80931e] rounded-full"></div>
|
||||||
<GrLocation />
|
|
||||||
<span className="text-sm lg:text-base">{cafeData.address || "آدرس موجود نیست"}</span>
|
|
||||||
</div>
|
|
||||||
<hr className="w-full lg:w-38 -mt-3 border-1 text-[#80931e]" />
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<FaRegStar />
|
|
||||||
<span className="text-sm lg:text-base">{cafeData.rating || 0}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="font-bold text-base">درباره کافه</h3>
|
<div className="flex items-center gap-3 text-[#402E32]">
|
||||||
<span className="text-sm lg:text-base">
|
<GrLocation className="text-lg flex-shrink-0" />
|
||||||
{cafeData.description || "توضیحاتی برای این کافه وجود ندارد."}
|
<span className="text-sm md:text-base">{cafeData.address || "آدرس موجود نیست"}</span>
|
||||||
</span>
|
|
||||||
<hr className="w-full lg:w-[104%] -mt-3 border-1 text-[#80931e]" />
|
|
||||||
|
|
||||||
<h3 className="text-[#402E32] font-bold mt-6 lg:mt-10 lg:relative lg:-right-64">
|
|
||||||
ویژگی ها
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:flex lg:relative lg:-mr-71 lg:-ml-7 mt-5 gap-3">
|
|
||||||
<div
|
|
||||||
className="bg-[#e1d5c2] w-full lg:w-[140px] h-[137.6px] flex flex-col items-center gap-4 lg:gap-8 text-center rounded-2xl lg:rounded-4xl transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<img src={Coffee3} className="w-6 h-6 text-[#402E32] mt-5" />
|
|
||||||
<span className="text-[#402E32] font-medium text-sm lg:text-base whitespace-nowrap px-2">
|
|
||||||
منو کافه:
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div className="flex items-center gap-3 text-[#402E32]">
|
||||||
className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
<FaRegStar className="text-lg flex-shrink-0" />
|
||||||
<img src={Vector15} className="w-6 h-6 text-[#402E32] mt-5" />
|
<span className="text-sm md:text-base">{cafeData.rating || 0}</span>
|
||||||
<span>ساعت کاری:</span>
|
|
||||||
<span> 23 - 8</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<img src={Vector14} className="w-6 h-6 text-[#402E32] mt-5" />
|
|
||||||
<span>رزرو :</span>
|
|
||||||
<span>رزرو آنلاین</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[170.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<img src={Vector11} className="w-6 h-6 text-[#402E32] mt-5" />
|
|
||||||
<span>موسیقی :</span>
|
|
||||||
<span>موسیقی زنده آخر هفته</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<img src={Vector13} className="w-6 h-6 text-[#402E32] mt-5" />
|
|
||||||
<span>پارکینگ :</span>
|
|
||||||
<span>عمومی</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="flex flex-col gap-3 lg:gap-4 text-[#402E32] font-medium w-full lg:w-[161.5px] h-[137.6px] bg-[#e1d5c2] rounded-2xl lg:rounded-4xl pr-3 text-sm lg:text-base transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<img src={Vector12} className="w-6 h-6 text-[#402E32] mt-5" />
|
|
||||||
<span>دسترسی آسان :</span>
|
|
||||||
<span>مناسب افراد ناتوان</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="flex justify-center items-center w-full lg:w-[150.5px] h-[137.6px] bg-[#5e5450] rounded-2xl lg:rounded-4xl transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<img src={Vector9} className="w-6 h-6 text-[#402E32]" />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-10 lg:-mr-64 text-[#402E32] font-medium w-full lg:w-[132%]">
|
<h3 className="font-bold text-[#402E32] mb-2">درباره کافه</h3>
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
|
<p className="text-sm md:text-base text-[#555]">
|
||||||
<div className="w-full sm:max-w-[930px] overflow-hidden flex-1">
|
{cafeData.description || "توضیحاتی برای این کافه وجود ندارد."}
|
||||||
<div className="categories-scroll flex gap-6 lg:gap-10 overflow-x-auto scrollbar-hide">
|
</p>
|
||||||
<div className="flex items-center gap-2 justify-center flex-shrink-0">
|
</div>
|
||||||
<img
|
</div>
|
||||||
src={Vector16}
|
</div>
|
||||||
alt="Logo"
|
|
||||||
className="cursor-pointer hover:opacity-70 transition-opacity"
|
|
||||||
onClick={() => setIsEditMode(!isEditMode)}
|
|
||||||
title={isEditMode ? "خروج از حالت ویرایش" : "ویرایش عنوانها"}
|
|
||||||
/>
|
|
||||||
<span className="whitespace-nowrap text-sm lg:text-base">عنوان</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isEditMode ? (
|
{/* Features Section */}
|
||||||
<>
|
<div className="mb-8">
|
||||||
{categories.map((category, index) => (
|
<h2 className="font-bold text-[#402E32] mb-4 text-base md:text-lg">ویژگی ها</h2>
|
||||||
<span key={index} className="whitespace-nowrap flex-shrink-0 text-sm lg:text-base">{category}</span>
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||||
|
{CAFE_FEATURES.map((feature, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="bg-[#e1d5c2] p-4 rounded-2xl flex flex-col items-center gap-3 text-center transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"
|
||||||
|
>
|
||||||
|
<img src={feature.icon} alt={feature.label} className="w-6 h-6" />
|
||||||
|
<span className="text-[#402E32] font-medium text-xs md:text-sm whitespace-normal">
|
||||||
|
{feature.label}
|
||||||
|
</span>
|
||||||
|
{feature.value && (
|
||||||
|
<span className="text-[#402E32] text-xs">{feature.value}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
<button className="bg-[#5e5450] p-4 rounded-2xl flex items-center justify-center transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
||||||
) : (
|
<img src={Vector9} alt="افزودن" className="w-6 h-6" />
|
||||||
<>
|
</button>
|
||||||
{categories.map((category, index) => (
|
</div>
|
||||||
<div key={index} className="flex flex-col items-center gap-1 flex-shrink-0">
|
</div>
|
||||||
{editingIndex === index ? (
|
|
||||||
<>
|
{/* Categories Section */}
|
||||||
<div className="flex items-center gap-1 mb-1">
|
<div className="mb-8">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<h2 className="font-bold text-[#402E32] text-base md:text-lg">دستهبندیها</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsEditMode(!isEditMode)}
|
||||||
|
className="flex items-center gap-2 text-[#7f4629] hover:text-[#5f494f] transition-colors"
|
||||||
|
title={isEditMode ? "خروج از حالت ویرایش" : "ویرایش دستهبندیها"}
|
||||||
|
>
|
||||||
|
<img src={Vector16} alt="ویرایش" className="w-5 h-5" />
|
||||||
|
<span className="text-sm md:text-base">{isEditMode ? "تمام" : "ویرایش"}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="categories-scroll flex gap-3 md:gap-4 overflow-x-auto scrollbar-hide pb-2">
|
||||||
|
{categories.map((category, idx) => (
|
||||||
|
<div key={idx} className="flex-shrink-0">
|
||||||
|
{isEditMode && editingIndex === idx ? (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex gap-1">
|
||||||
<IoMdCheckmark
|
<IoMdCheckmark
|
||||||
className="text-green-600 cursor-pointer hover:text-green-800 w-4 h-4 flex-shrink-0"
|
onClick={handleSaveCategory}
|
||||||
onClick={handleSaveEdit}
|
className="text-green-600 cursor-pointer hover:text-green-800 w-5 h-5"
|
||||||
title="ذخیره"
|
title="ذخیره"
|
||||||
/>
|
/>
|
||||||
<IoMdClose
|
<IoMdClose
|
||||||
className="text-red-600 cursor-pointer hover:text-red-800 w-4 h-4 flex-shrink-0"
|
|
||||||
onClick={handleCancelEdit}
|
onClick={handleCancelEdit}
|
||||||
|
className="text-red-600 cursor-pointer hover:text-red-800 w-5 h-5"
|
||||||
title="لغو"
|
title="لغو"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -312,58 +308,59 @@ export default function EditCafe() {
|
||||||
type="text"
|
type="text"
|
||||||
value={editValue}
|
value={editValue}
|
||||||
onChange={(e) => setEditValue(e.target.value)}
|
onChange={(e) => setEditValue(e.target.value)}
|
||||||
className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-sm focus:outline-none focus:border-[#7f4629] w-[100px] text-center"
|
className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-xs md:text-sm focus:outline-none focus:border-[#7f4629] min-w-[100px]"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<div className={`flex items-center gap-2 px-3 py-2 rounded-lg text-[#402E32] text-xs md:text-sm whitespace-nowrap ${
|
||||||
|
isEditMode
|
||||||
|
? "bg-gray-100 hover:bg-gray-200"
|
||||||
|
: "bg-[#e1d5c2]"
|
||||||
|
} transition-colors`}>
|
||||||
|
<span>{category}</span>
|
||||||
|
{isEditMode && (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<BiEdit
|
<BiEdit
|
||||||
className="text-[#7f4629] cursor-pointer hover:text-[#5f494f] w-5 h-5 flex-shrink-0"
|
onClick={() => handleEditCategory(idx)}
|
||||||
onClick={() => handleStartEdit(index)}
|
className="text-[#7f4629] cursor-pointer hover:text-[#5f494f] w-4 h-4 ml-1"
|
||||||
title="ویرایش"
|
title="ویرایش"
|
||||||
/>
|
/>
|
||||||
<span className="whitespace-nowrap text-sm lg:text-base">{category}</span>
|
|
||||||
<IoMdClose
|
<IoMdClose
|
||||||
className="text-white bg-[#a79fa1] rounded-md cursor-pointer size-5 flex-shrink-0"
|
onClick={() => handleDeleteCategory(idx)}
|
||||||
onClick={() => handleDeleteCategory(index)}
|
className="text-white bg-[#a79fa1] rounded cursor-pointer hover:bg-[#9a8f91] w-4 h-4"
|
||||||
title="حذف"
|
title="حذف"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isEditMode && (
|
{isEditMode && !isAddingCategory && (
|
||||||
<div className="flex-shrink-0 w-full sm:w-auto" style={{ minWidth: '120px' }}>
|
|
||||||
{!isAdding ? (
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsAdding(true)}
|
onClick={() => setIsAddingCategory(true)}
|
||||||
className="text-[#7f4629] hover:text-[#5f494f] font-bold transition-colors whitespace-nowrap w-full text-sm lg:text-base"
|
className="flex-shrink-0 text-[#7f4629] hover:text-[#5f494f] font-bold text-sm md:text-base transition-colors whitespace-nowrap"
|
||||||
title="اضافه کردن عنوان جدید"
|
|
||||||
>
|
>
|
||||||
+ افزودن
|
+ افزودن
|
||||||
</button>
|
</button>
|
||||||
) : (
|
)}
|
||||||
<div className="flex flex-col items-center gap-1">
|
|
||||||
<div className="flex items-center gap-1 mb-1">
|
{isEditMode && isAddingCategory && (
|
||||||
|
<div className="flex-shrink-0 flex flex-col gap-2">
|
||||||
|
<div className="flex gap-1">
|
||||||
<IoMdCheckmark
|
<IoMdCheckmark
|
||||||
className="text-green-600 cursor-pointer hover:text-green-800 w-4 h-4 flex-shrink-0"
|
|
||||||
onClick={handleAddCategory}
|
onClick={handleAddCategory}
|
||||||
|
className="text-green-600 cursor-pointer hover:text-green-800 w-5 h-5"
|
||||||
title="ذخیره"
|
title="ذخیره"
|
||||||
/>
|
/>
|
||||||
<IoMdClose
|
<IoMdClose
|
||||||
className="text-white bg-[#a79fa1] rounded-md cursor-pointer w-4 h-4 flex-shrink-0"
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsAdding(false);
|
setIsAddingCategory(false);
|
||||||
setNewCategory("");
|
setNewCategory("");
|
||||||
}}
|
}}
|
||||||
|
className="text-red-600 cursor-pointer hover:text-red-800 w-5 h-5"
|
||||||
title="لغو"
|
title="لغو"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -371,89 +368,38 @@ export default function EditCafe() {
|
||||||
type="text"
|
type="text"
|
||||||
value={newCategory}
|
value={newCategory}
|
||||||
onChange={(e) => setNewCategory(e.target.value)}
|
onChange={(e) => setNewCategory(e.target.value)}
|
||||||
placeholder="عنوان جدید"
|
placeholder="دسته جدید"
|
||||||
className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-sm focus:outline-none focus:border-[#7f4629] w-[100px] text-center"
|
className="border-2 border-[#bb8f70] rounded-lg px-2 py-1 text-xs md:text-sm focus:outline-none focus:border-[#7f4629] min-w-[100px]"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<hr className="w-full lg:w-[132%] lg:-mr-66 mt-3 border-2 rounded-3xl text-[#939393]" />
|
|
||||||
|
|
||||||
<div className="mt-9 flex items-center gap-2 lg:-mr-64 text-[#a79fa1] font-bold text-sm lg:text-base">
|
|
||||||
<img src={Vector16} alt="Logo" />
|
|
||||||
<h3>افزودن زیر عنوان</h3>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-9 flex items-center gap-2 lg:-mr-64 text-[#66585b] font-bold text-sm lg:text-base">
|
{/* Products Section */}
|
||||||
<img className="w-5.5 h-5.5" src={Edit} alt="Logo" />
|
<div>
|
||||||
<img className="w-6 h-6" src={Coffee3} alt="Logo" />
|
<h2 className="font-bold text-[#402E32] mb-4 text-base md:text-lg">محصولات</h2>
|
||||||
<h3>قهوه ها</h3>
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6">
|
||||||
<hr className="mt-9 -mr-15 w-[5%] border-1 text-[#80931e] hidden lg:block" />
|
{CAFE_PRODUCTS.map((product, idx) => (
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-9 flex items-center gap-2 lg:-mr-64 text-[#66585b] font-bold text-sm lg:text-base">
|
|
||||||
<img src={Vector16} alt="Logo" />
|
|
||||||
<h3>آیتم</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 lg:-mr-64">
|
|
||||||
<div
|
<div
|
||||||
className="mt-8 lg:mt-13 flex items-center flex-col justify-center w-full lg:w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
key={idx}
|
||||||
<h1 className="text-[#402E32] font-bold text-sm lg:text-base lg:-mr-50">اسپرسو100%</h1>
|
className="bg-white border border-gray-200 rounded-xl p-4 transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md"
|
||||||
<img src={Sperso} alt="Logo" className="mt-5" />
|
>
|
||||||
<div className="flex justify-between gap-20 lg:gap-50 mt-3 text-[#66585b] font-medium text-sm lg:text-base">
|
<h3 className="font-bold text-[#402E32] text-sm md:text-base mb-3">{product.name}</h3>
|
||||||
<span>قیمت</span>
|
<img src={product.image} alt={product.name} className="w-full h-40 object-cover rounded-lg mb-3" />
|
||||||
<span>118.000</span>
|
<div className="flex justify-between items-center mb-3 text-sm">
|
||||||
</div>
|
<span className="text-[#66585b]">قیمت:</span>
|
||||||
<span className="font-light text-[#66585b] mt-2 break-words px-4 lg:mr-4 text-xs lg:text-[14.90px]">
|
<span className="font-medium text-[#402E32]">{product.price}</span>
|
||||||
45 میلی لیتر، قهوه، 100% عربیکا، دم شده با دستگاه اسپرسو ساز،
|
|
||||||
به همراه یک عدد آب معدنی مینی
|
|
||||||
</span>
|
|
||||||
<hr className="mt-5 w-[90%] border-3 rounded-4xl text-[#e6e2de]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="mt-8 lg:mt-10 flex items-center flex-col justify-center w-full lg:w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<h1 className="text-[#402E32] font-bold text-sm lg:text-base lg:-mr-50">
|
|
||||||
کارامل ماکیاتو
|
|
||||||
</h1>
|
|
||||||
<img src={Coffee1} alt="Logo" className="mt-5" />
|
|
||||||
<div className="flex justify-between gap-20 lg:gap-50 mt-3 text-[#66585b] font-medium text-sm lg:text-base">
|
|
||||||
<span>قیمت</span>
|
|
||||||
<span>149.000</span>
|
|
||||||
</div>
|
|
||||||
<span className="font-light text-[#66585b] mt-2 break-words px-4 lg:mr-4 text-xs lg:text-[14.90px]">
|
|
||||||
220 میلی لیتر، 2 شات اسپرسو 30% روبوستا، 70% عربیکا، یک لکه
|
|
||||||
فوم شیر، سیروپ کارامل
|
|
||||||
</span>
|
|
||||||
<hr className="mt-5 w-[90%] border-3 rounded-4xl text-[#e6e2de]" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="mt-8 lg:mt-10 flex items-center flex-col justify-center w-full lg:w-[320px] h-[391px] transform hover:-translate-y-1 transition-all duration-300 hover:shadow-md">
|
|
||||||
<h1 className="text-[#402E32] font-bold text-sm lg:text-base lg:-mr-47">
|
|
||||||
اسپرسو آفوگاتو
|
|
||||||
</h1>
|
|
||||||
<img src={Coffee2} alt="Logo" className="mt-5" />
|
|
||||||
<div className="flex justify-between gap-20 lg:gap-50 mt-3 text-[#66585b] font-medium text-sm lg:text-base">
|
|
||||||
<span>قیمت</span>
|
|
||||||
<span>118.000</span>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
className="font-light text-[#66585b] mt-2 mb-10 break-words px-4 lg:-mr-23 text-xs lg:text-[14.90px]">
|
|
||||||
اسپرسو، یک اسکوپ بستنی وانیلی
|
|
||||||
</span>
|
|
||||||
<hr className="w-[90%] border-3 rounded-4xl text-[#e6e2de]" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<p className="text-xs md:text-sm text-[#66585b] line-clamp-3">
|
||||||
|
{product.description}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
import {useProfile} from "../../hooks/useProfile";
|
import React from "react";
|
||||||
|
import { useProfile } from "../../hooks/useProfile";
|
||||||
|
|
||||||
const Dashboard = () => {
|
export default function Dashboard() {
|
||||||
useProfile();
|
useProfile();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<section dir="rtl" className="w-full">
|
||||||
صفحه داشبورد
|
<h1 className="text-2xl font-bold text-[#402E32] mb-6">صفحه داشبورد</h1>
|
||||||
|
<div className="bg-white border-2 border-[#8b8886] rounded-2xl p-6 md:p-8">
|
||||||
|
<p className="text-gray-600">محتوای داشبورد بهزودی اضافه خواهد شد</p>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Dashboard;
|
|
||||||
|
|
@ -1,202 +1,179 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import Loginpic from '../../assets/image/loginpic.jpg';
|
import { useNavigate } from "react-router-dom";
|
||||||
import { FiLock } from 'react-icons/fi';
|
import { useDispatch } from "react-redux";
|
||||||
|
import { FiLock } from "react-icons/fi";
|
||||||
import { FaRegUser } from "react-icons/fa6";
|
import { FaRegUser } from "react-icons/fa6";
|
||||||
|
|
||||||
|
// Assets
|
||||||
|
import Loginpic from "../../assets/image/loginpic.jpg";
|
||||||
import LogoDM from "../../assets/icons/LogoDM.svg";
|
import LogoDM from "../../assets/icons/LogoDM.svg";
|
||||||
import { useNavigate, Navigate } from 'react-router-dom';
|
|
||||||
import authService from '../../services/auth';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { setToken } from '../../redux/slices/authSlice';
|
|
||||||
import { setProfile } from '../../redux/slices/profileSlice';
|
|
||||||
|
|
||||||
|
// Redux
|
||||||
|
import { setToken } from "../../redux/slices/authSlice";
|
||||||
|
import { setProfile } from "../../redux/slices/profileSlice";
|
||||||
|
|
||||||
const Login = () => {
|
// Services
|
||||||
const [userName, setUserName] = useState('');
|
import authService from "../../services/auth";
|
||||||
const [password, setPassword] = useState('');
|
|
||||||
const [loading, setLoading] = useState(false);
|
export default function Login() {
|
||||||
const [error, setError] = useState('');
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
// State Management
|
||||||
|
const [userName, setUserName] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
// Handle Login
|
||||||
const handleLogin = async (e) => {
|
const handleLogin = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError('');
|
setError("");
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
||||||
const loginData = {
|
|
||||||
userName: userName,
|
|
||||||
password: password
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await authService.login(loginData);
|
const res = await authService.login({ userName, password });
|
||||||
if (res.data.success && res.data.data && res.data.data.tokens.accessToken) {
|
if (res.data.success && res.data.data?.tokens?.accessToken) {
|
||||||
dispatch(setToken(res.data.data.tokens.accessToken));
|
dispatch(setToken(res.data.data.tokens.accessToken));
|
||||||
navigate('/dashboard');
|
|
||||||
dispatch(setProfile(res.data.data.admin));
|
dispatch(setProfile(res.data.data.admin));
|
||||||
console.log('login:', res.data.data.admin);
|
console.log("✅ Login successful:", res.data.data.admin);
|
||||||
|
navigate("/dashboard");
|
||||||
|
} else {
|
||||||
|
setError("اطلاعات دریافتی معتبر نیست");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Response:', res);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
console.error("❌ Login error:", error);
|
||||||
console.error('❌ Login error:', error);
|
const errorMessage =
|
||||||
// if (error.response) {
|
error.response?.data?.message ||
|
||||||
// setError(error.response.data.message || error.response.data.en_message || 'نام کاربری یا رمز عبور اشتباه است');
|
(error.request ? "خطا در برقراری ارتباط با سرور" : "خطای نامشخص رخ داده است");
|
||||||
// } else if (error.request) {
|
setError(errorMessage);
|
||||||
// setError('خطا در برقراری ارتباط با سرور');
|
|
||||||
// } else {
|
|
||||||
// console.error('Error setting up request');
|
|
||||||
// setError('خطای نامشخص رخ داده است');
|
|
||||||
// }
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-screen" dir="rtl">
|
<div className="relative min-h-screen" dir="rtl">
|
||||||
<div className="absolute top-0 left-0 right-0 bg-white z-10 shadow-sm">
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between px-8 py-4">
|
<header className="fixed top-0 left-0 right-0 bg-white z-10 shadow-sm">
|
||||||
|
<div className="flex items-center justify-between px-6 md:px-8 lg:px-12 py-4">
|
||||||
|
{/* Left Section */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<div className="flex justify-center">
|
<img src={LogoDM} alt="Logo" className="h-10 w-10 md:h-12 md:w-12" />
|
||||||
<img src={LogoDM} className="h-12 w-12" alt="Logo" />
|
|
||||||
</div>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<select className="appearance-none px-6 py-2 pr-4 pl-10 bg-white border-2 border-gray-300 rounded-xl text-sm focus:outline-none focus:border-[#7f4629] transition-colors cursor-pointer">
|
<select className="appearance-none px-4 md:px-6 py-2 pr-4 pl-10 bg-white border-2 border-gray-300 rounded-xl text-sm focus:outline-none focus:border-[#7f4629] transition-colors cursor-pointer">
|
||||||
<option>شهر</option>
|
<option>شهر</option>
|
||||||
<option>تهران</option>
|
<option>تهران</option>
|
||||||
<option>اصفهان</option>
|
<option>اصفهان</option>
|
||||||
<option>شیراز</option>
|
<option>شیراز</option>
|
||||||
<option>مشهد</option>
|
<option>مشهد</option>
|
||||||
</select>
|
</select>
|
||||||
<svg
|
<svg className="absolute left-4 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-500 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
className="absolute left-4 top-5 -translate-y-1/2 w-6 h-10 text-gray-500 pointer-events-none"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center font-bold gap-16 text-[#402E32]">
|
{/* Center Links (Hidden on Mobile) */}
|
||||||
<a href="#" className="hover:text-[#7f4629] transition-colors">
|
<nav className="hidden md:flex items-center gap-8 font-bold text-[#402E32]">
|
||||||
خانه
|
<a href="#" className="hover:text-[#7f4629] transition-colors text-sm">خانه</a>
|
||||||
</a>
|
<a href="#" className="hover:text-[#7f4629] transition-colors text-sm">دسته بندی</a>
|
||||||
<a href="#" className="hover:text-[#7f4629] transition-colors">
|
<a href="#" className="hover:text-[#7f4629] transition-colors text-sm">تماس با ما</a>
|
||||||
دسته بندی
|
<a href="#" className="hover:text-[#7f4629] transition-colors text-sm">درباره ما</a>
|
||||||
</a>
|
</nav>
|
||||||
<a href="#" className="hover:text-[#7f4629] transition-colors">
|
|
||||||
تماس با ما
|
|
||||||
</a>
|
|
||||||
<a href="#" className="hover:text-[#7f4629] transition-colors">
|
|
||||||
درباره ما
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
{/* Right Section */}
|
||||||
<button className="px-12 py-2 bg-[#7f4629] text-white rounded-full font-medium hover:bg-[#5f494f] transition-colors">
|
<button className="px-6 md:px-10 py-2 bg-[#7f4629] text-white rounded-full text-sm md:text-base font-medium hover:bg-[#5f494f] transition-colors">
|
||||||
ثبت نام
|
ثبت نام
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</header>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex h-full pt-20">
|
{/* Main Content */}
|
||||||
<div className="w-1/2 flex items-start justify-start bg-[#f5f0e8] px-24 pt-16">
|
<div className="flex min-h-screen pt-20">
|
||||||
<div className="w-full max-w-lg">
|
{/* Left Side - Login Form */}
|
||||||
<div className="flex items-center gap-2 text-sm text-gray-500 mb-8">
|
<div className="w-full md:w-1/2 flex items-start justify-center md:justify-start bg-[#f5f0e8] px-4 md:px-12 lg:px-24 py-8 md:py-16">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* Breadcrumb */}
|
||||||
|
<div className="flex items-center gap-2 text-xs md:text-sm text-gray-500 mb-8">
|
||||||
<span>ورود</span>
|
<span>ورود</span>
|
||||||
<span className="font-bold text-2xl">></span>
|
<span className="font-bold text-lg">></span>
|
||||||
<span>خانه</span>
|
<span>خانه</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h2 className="text-2xl font-bold text-[#402E32] mb-2">
|
<h1 className="text-2xl md:text-3xl font-bold text-[#402E32] mb-2">ورود ادمین</h1>
|
||||||
ورود ادمین
|
<p className="text-sm md:text-base text-gray-700">دسترسی ویژه برای مدیریت و گزارشها</p>
|
||||||
</h2>
|
|
||||||
<p className="text-gray-700 text-sm">
|
|
||||||
دسترسی ویژه برای مدیریت و گزارشها
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Login Form */}
|
||||||
<form onSubmit={handleLogin} className="space-y-6">
|
<form onSubmit={handleLogin} className="space-y-6">
|
||||||
|
{/* Error Message */}
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl text-sm">
|
<div className="bg-red-50 border-2 border-red-300 text-red-700 px-4 py-3 rounded-xl text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Username Field */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<label className="absolute -top-2.5 right-4 bg-[#f5f0e8] px-2 text-sm text-gray-600 z-10">
|
<label className="absolute -top-2.5 right-4 bg-[#f5f0e8] px-2 text-sm text-gray-600">نام کاربری</label>
|
||||||
نام کاربری
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={userName}
|
value={userName}
|
||||||
onChange={(e) => setUserName(e.target.value)}
|
onChange={(e) => setUserName(e.target.value)}
|
||||||
placeholder="userName"
|
placeholder="userName"
|
||||||
className="w-full pl-14 pr-6 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors"
|
className="w-full pl-12 pr-4 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors text-sm"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<FaRegUser className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-400 w-6 h-5" />
|
<FaRegUser className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Password Field */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<label className="absolute -top-2.5 right-4 bg-[#f5f0e8] px-2 text-sm text-gray-600 z-10">
|
<label className="absolute -top-2.5 right-4 bg-[#f5f0e8] px-2 text-sm text-gray-600">رمز عبور</label>
|
||||||
رمز عبور
|
|
||||||
</label>
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="password"
|
placeholder="password"
|
||||||
className="w-full pl-14 pr-6 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors"
|
className="w-full pl-12 pr-4 py-3 border-2 border-gray-300 rounded-xl focus:outline-none focus:border-[#7f4629] transition-colors text-sm"
|
||||||
dir="ltr"
|
dir="ltr"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<FiLock className="absolute left-5 top-1/2 -translate-y-1/2 text-gray-400 w-6 h-10" />
|
<FiLock className="absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Forgot Password */}
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<a
|
<a href="#" className="text-sm text-gray-500 hover:text-[#7f4629] transition-colors">
|
||||||
href="#"
|
فراموشی رمز عبور؟
|
||||||
className="text-sm text-gray-500 hover:text-[#7f4629] transition-colors"
|
|
||||||
>
|
|
||||||
فراموشی رمز عبور
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={loading || !userName || !password}
|
disabled={loading || !userName || !password}
|
||||||
className="w-full bg-[#7f4629] text-white py-3 rounded-full font-bold hover:bg-[#5f494f] transition-all duration-300 shadow-lg hover:shadow-xl disabled:bg-gray-400 disabled:cursor-not-allowed"
|
className="w-full bg-[#7f4629] text-white py-3 rounded-full font-bold text-sm md:text-base hover:bg-[#5f494f] transition-all duration-300 shadow-lg hover:shadow-xl disabled:bg-gray-400 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
{loading ? 'در حال ورود...' : 'ورود'}
|
{loading ? "در حال ورود..." : "ورود"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-1/2 relative overflow-hidden">
|
{/* Right Side - Image (Hidden on Mobile) */}
|
||||||
<img
|
<div className="hidden md:block w-1/2 relative overflow-hidden">
|
||||||
src={Loginpic}
|
<img src={Loginpic} alt="Coffee workspace" className="w-full h-full object-cover" />
|
||||||
alt="Coffee workspace"
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default Login;
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
const Stats = () => {
|
import React from "react";
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
صفحه آمار و تحلیل
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Stats;
|
export default function Stats() {
|
||||||
|
return (
|
||||||
|
<section dir="rtl" className="w-full">
|
||||||
|
<h1 className="text-2xl font-bold text-[#402E32] mb-6">آمار و تحلیل</h1>
|
||||||
|
<div className="bg-white border-2 border-[#8b8886] rounded-2xl p-6 md:p-8">
|
||||||
|
<p className="text-gray-600">محتوای آمار و تحلیل بهزودی اضافه خواهد شد</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue