تبدیل صفحه dashboard پنل به Server Component که آمار ماهانه نماینده را از API میگیرد (year/month میلادی جاری) و با adaptRepresentationDashboard به propهای کامپوننت map میکند. حذف داده mock. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🏥 نوبت724 (Nobat724) - سیستم نوبتدهی آنلاین پزشکی
📋 فهرست مطالب
- معرفی پروژه
- معماری و تکنولوژی
- ساختار پروژه
- ویژگیهای کلیدی
- راهاندازی پروژه
- سیستم Multi-Domain
- احراز هویت و مجوزها
- مسیرها و صفحات
- کامپوننتهای اصلی
- API و سرویسها
- Deployment
🎯 معرفی پروژه
نوبت724 یک پلتفرم جامع و پیشرفته برای نوبتدهی آنلاین پزشکی است که امکان رزرو نوبت از پزشکان و کلینیکها را به صورت آنلاین فراهم میکند.
ویژگیهای اصلی:
- ✅ Multi-Domain Architecture: پشتیبانی از دامنههای مختلف برای شهرهای مختلف
- ✅ رزرو نوبت آنلاین: سیستم کامل رزرو نوبت با تقویم شمسی
- ✅ پنل مدیریتی: پنل کامل برای نمایندگان و پزشکان
- ✅ احراز هویت: سیستم ورود و احراز هویت با OTP
- ✅ پرداخت آنلاین: سیستم پرداخت یکپارچه
- ✅ Responsive Design: طراحی کاملاً واکنشگرا با Tailwind CSS
- ✅ Dark Mode: پشتیبانی کامل از حالت تاریک
- ✅ SEO Optimized: بهینهسازی کامل SEO با متادیتای داینامیک
🏗️ معماری و تکنولوژی
Frontend Framework
- Next.js 14.2.20 (App Router)
- React 18
- Server Components و Client Components
UI/UX
- Material-UI (MUI) v7: کامپوننتهای UI
- Tailwind CSS 3.4: استایلدهی
- Emotion: CSS-in-JS
- Dark Mode: با
next-themes - Responsive Design: Mobile-first approach
State Management & Data Fetching
- React Context API: مدیریت state سراسری
- Axios: درخواستهای HTTP
- Server-Side Rendering (SSR)
Authentication & Authorization
- CASL: مدیریت دسترسیها
- JWT Tokens: احراز هویت
- Cookie-based Auth: ذخیره توکنها
کتابخانههای تخصصی
- jalali-moment & moment-jalaali: تبدیل تاریخ شمسی
- jalaali-react-date-picker: Date Picker فارسی
- react-toastify: نوتیفیکیشنها
- html2canvas & jspdf: تولید PDF
- qrcode.react: تولید QR Code
- @maptiler/sdk: نمایش نقشه
Development Tools
- Docker & Docker Compose: Containerization
- ESLint: Code Quality
- Prettier: Code Formatting
📁 ساختار پروژه
nobat724_front/
├── app/ # Next.js App Directory
│ ├── layout.js # Root Layout با Metadata داینامیک
│ ├── page.js # صفحه اصلی
│ ├── globals.css # استایلهای سراسری
│ ├── about-us/ # درباره ما
│ ├── appointment/[doctorId]/ # صفحه رزرو نوبت (Dynamic Route)
│ ├── blog/[slug]/ # صفحه جزئیات بلاگ
│ ├── blogs/ # لیست بلاگها
│ ├── clinic/[slug]/ # صفحه جزئیات کلینیک
│ ├── clinics/ # لیست کلینیکها
│ ├── contact-us/ # تماس با ما
│ ├── dashboard/ # داشبورد عمومی
│ ├── doctor/[slug]/ # صفحه جزئیات پزشک
│ ├── doctors/ # لیست پزشکان
│ ├── login/ # ورود
│ ├── login-verify/ # تایید OTP
│ ├── panel/(layout)/ # پنل مدیریتی (Route Group)
│ │ ├── dashboard/ # داشبورد پنل
│ │ ├── user-account/ # حساب کاربری
│ │ ├── add-doctor/ # افزودن پزشک
│ │ └── turns/ # مدیریت نوبتها
│ ├── specialties/ # تخصصهای پزشکی
│ └── component/ # کامپوننتهای مشترک App
│
├── components/ # کامپوننتهای اصلی
│ ├── home/ # کامپوننتهای صفحه اصلی
│ ├── appointment/ # کامپوننتهای رزرو نوبت
│ ├── doctor/ # کامپوننتهای پزشک
│ ├── doctors/ # کامپوننتهای لیست پزشکان
│ ├── clinic/ # کامپوننتهای کلینیک
│ ├── clinics/ # کامپوننتهای لیست کلینیکها
│ ├── panel/ # کامپوننتهای پنل
│ ├── layout/ # Layout کامپوننتها
│ ├── layoutPanel/ # Layout پنل
│ ├── register/ # ثبتنام و ورود
│ └── icons/ # آیکونهای سفارشی
│
├── lib/ # کتابخانهها و Utilities
│ ├── ability.js # تعریف دسترسیها (CASL)
│ ├── auth.js # احراز هویت
│ ├── getStateInfo.js # دریافت اطلاعات استان/شهر (Server)
│ ├── getStateInfoClient.js # دریافت اطلاعات استان/شهر (Client)
│ ├── getCanonicalUrl.js # تولید Canonical URL
│ └── req.js # Helper برای Fetch
│
├── services/ # سرویسهای API
│ ├── api.js # تنظیمات Axios
│ ├── response.js # API Endpoints
│ └── clinicApi.js # APIهای مربوط به کلینیک
│
├── context/ # React Context
│ └── ProvinceProvider.js # Context استان/شهر
│
├── data/ # فایلهای JSON استاتیک
│ ├── city.json # اطلاعات شهرها (Multi-Domain)
│ ├── state.json # اطلاعات استانها
│ ├── specialties.json # تخصصهای پزشکی
│ ├── doctors.json # Mock Data پزشکان
│ └── clinics.json # Mock Data کلینیکها
│
├── helper/ # توابع کمکی
│ └── index.js # Utility Functions
│
├── utils/ # Utilities
│ └── index.js # توابع کمکی (removeToken و...)
│
├── hooks/ # Custom Hooks
│ └── useCanonicalUrl.js # Hook برای Canonical URL
│
├── mui/ # تنظیمات MUI
│ └── index.js # Theme Configuration
│
├── public/ # فایلهای استاتیک
│ ├── assets/ # تصاویر و آیکونها
│ └── fonts/ # فونتها
│
├── middleware.js # Next.js Middleware
├── next.config.js # تنظیمات Next.js
├── tailwind.config.js # تنظیمات Tailwind
├── docker-compose.yml # Docker Compose
├── dockerfile # Dockerfile
└── package.json # Dependencies
✨ ویژگیهای کلیدی
1. Multi-Domain Architecture
پروژه از معماری Multi-Domain پشتیبانی میکند که به هر شهر اجازه میدهد دامنه مستقل خود را داشته باشد:
// در data/city.json
{
"id": "600",
"domain": "nobat724.com", // دامنه اصلی
"site_name": "نوبت724",
"title": "رزرو نوبت پزشکی",
// ...
}
نحوه عملکرد:
- در
lib/getStateInfo.js: بررسی subdomain و تطبیق با شهر - در
app/layout.js: تولید Metadata داینامیک بر اساس شهر - در
context/ProvinceProvider.js: مدیریت Context شهر/استان
2. سیستم احراز هویت
// lib/auth.js - دریافت اطلاعات کاربر از Cookie
export async function getUser() {
const cookieStore = cookies();
const raw =
cookieStore.get("access_token") &&
cookieStore.get("refresh_token") &&
cookieStore.get("uuid") &&
cookieStore.get("userInfo");
return raw ? raw : null;
}
// lib/ability.js - تعریف دسترسیها
export function defineAbilitiesFor(user) {
if (user) {
can("access", "Dashboard");
const parsedData = JSON.parse(user.value);
if (parsedData.roles.find((item) => item === "representation")) {
can("access", "Panel");
}
} else {
can("access", "Login");
}
}
3. سیستم رزرو نوبت
فرآیند رزرو نوبت شامل مراحل زیر است:
- انتخاب تاریخ و زمان (
components/appointment/date/) - ورود یا ثبتنام (در صورت نیاز)
- وارد کردن اطلاعات بیمار (
components/appointment/detail/) - پرداخت (
components/appointment/paying/) - تایید نهایی (
components/appointment/successPay/)
4. پنل مدیریتی
پنل مدیریتی برای نمایندگان شامل:
- داشبورد: نمای کلی آمار
- حساب کاربری: مدیریت پروفایل
- افزودن پزشک: ثبت پزشک جدید
- مدیریت نوبتها: مشاهده و مدیریت نوبتها
محافظت مسیر:
// app/panel/(layout)/layout.js
async function LayoutPanel({ children }) {
const user = await getUser();
const ability = defineAbilitiesFor(user);
if (!ability.can("access", "Panel")) {
return redirect("/");
}
return <Content>{children}</Content>;
}
🚀 راهاندازی پروژه
پیشنیازها
- Node.js 18+
- npm یا yarn
- Docker (اختیاری)
نصب و راهاندازی
# 1. کلون کردن پروژه
git clone [repository-url]
cd nobat724_front
# 2. نصب وابستگیها
npm install
# یا
yarn install
# 3. ایجاد فایل .env.local
cp .env.example .env.local
# 4. تنظیم متغیرهای محیطی
# ویرایش .env.local:
NEXT_PUBLIC_API_URL=http://localhost:8000
NEXT_PUBLIC_CLIENT_ID=your_client_id
NEXT_PUBLIC_CLIENT_SECRET=your_client_secret
DEV_MODE=TRUE
# 5. اجرای پروژه در حالت Development
npm run dev
# یا
yarn dev
# پروژه در http://localhost:3000 اجرا میشود
اسکریپتهای موجود
{
"dev": "cross-env HOST=yazd-nobat.localhost PORT=3000 next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
راهاندازی با Docker
# Build و اجرا
docker-compose up -d
# مشاهده لاگها
docker-compose logs -f
# توقف
docker-compose down
🌐 سیستم Multi-Domain
ساختار دامنهها
پروژه از subdomain-based routing استفاده میکند:
nobat724.com → سایت اصلی (همه شهرها)
tabriz-nobat.ir → تبریز
urmia-nobat.ir → ارومیه
yazd-nobat.ir → یزد
فرآیند تشخیص شهر
// lib/getStateInfo.js (Server-Side)
export function getStateInfo() {
const headersList = headers();
const host = headersList.get("host") || "";
const subdomain = host.split(".")[0];
const matchedCity = citiesData.find((city) =>
city.domain.includes(subdomain)
);
return { matchedCity, matchedState };
}
// context/ProvinceProvider.js (Client-Side)
export const ProvinceProvider = ({ children }) => {
const [hostName, setHostName] = useState("");
const isProvinceInclude = citiesData.find((city) =>
hostName.includes(city.domain.split(".")[0])
);
return (
<ProvinceContext.Provider value={{ isProvinceInclude }}>
{children}
</ProvinceContext.Provider>
);
};
Metadata داینامیک بر اساس شهر
// app/layout.js
export async function generateMetadata() {
const { matchedCity } = getStateInfo();
return {
title: matchedCity ? matchedCity.title : "نوبت724",
description: matchedCity ? matchedCity.description : "توضیحات پیشفرض",
keywords: matchedCity ? matchedCity.keywords : "کلمات کلیدی پیشفرض",
};
}
🔐 احراز هویت و مجوزها
فرآیند ورود
- ورود شماره موبایل (
/login) - دریافت کد OTP
- تایید کد (
/login-verify) - ذخیره توکنها در Cookie
- Redirect به Dashboard یا Panel
Cookies استفاده شده
access_token: JWT Tokenrefresh_token: Refresh Tokenuuid: شناسه کاربرuserInfo: اطلاعات کاربر
محافظت از مسیرها
مثال - محافظت از Panel:
// app/panel/(layout)/layout.js
async function LayoutPanel({ children }) {
const user = await getUser();
const ability = defineAbilitiesFor(user);
if (!ability.can("access", "Panel")) {
return redirect("/");
}
return <Content>{children}</Content>;
}
مثال - محافظت از صفحه Login:
// app/login/page.js
async function LogIn() {
const user = await getUser();
const ability = defineAbilitiesFor(user);
if (!ability.can("access", "Login")) {
return redirect("/");
}
return <ContentLogin />;
}
📍 مسیرها و صفحات
صفحات عمومی (Public)
| مسیر | توضیحات | فایل |
|---|---|---|
/ |
صفحه اصلی | app/page.js |
/doctors |
لیست پزشکان | app/doctors/page.js |
/doctor/[slug] |
جزئیات پزشک | app/doctor/[slug]/page.js |
/clinics |
لیست کلینیکها | app/clinics/page.js |
/clinic/[slug] |
جزئیات کلینیک | app/clinic/[slug]/page.js |
/specialties |
تخصصهای پزشکی | app/specialties/page.js |
/blogs |
لیست بلاگها | app/blogs/page.js |
/blog/[slug] |
جزئیات بلاگ | app/blog/[slug]/page.js |
/about-us |
درباره ما | app/about-us/page.js |
/contact-us |
تماس با ما | app/contact-us/page.js |
صفحات رزرو نوبت
| مسیر | توضیحات |
|---|---|
/appointment/[doctorId] |
صفحه رزرو نوبت |
صفحات احراز هویت
| مسیر | توضیحات | محافظت |
|---|---|---|
/login |
ورود | فقط کاربران مهمان |
/login-verify |
تایید OTP | فقط کاربران مهمان |
پنل مدیریتی (Protected)
| مسیر | توضیحات | دسترسی |
|---|---|---|
/panel/dashboard |
داشبورد | نمایندگان |
/panel/user-account |
حساب کاربری | نمایندگان |
/panel/add-doctor |
افزودن پزشک | نمایندگان |
/panel/turns |
مدیریت نوبتها | نمایندگان |
🧩 کامپوننتهای اصلی
Layout Components
1. Root Layout (app/layout.js)
- Metadata داینامیک
- Theme Provider
- Province Provider
- Google Analytics
- Custom Toastify
2. Public Layout (components/layout/StLayout.js)
- Header
- Footer
- ScrollToTop
3. Panel Layout (components/layoutPanel/)
- Sidebar
- Header
- User Detail
Feature Components
Home Page (components/home/)
- Search Bar
- Text Header
- Frequent Searches
Doctor List (components/doctors/)
- Filter Sidebar
- Doctor Cards
- Pagination
Doctor Detail (components/doctor/)
- Doctor Info
- Appointment List
- Comments
- Share
Appointment (components/appointment/)
- Date Picker
- Time Slots
- Patient Details
- Payment
- Success/Fail Pages
Panel (components/panel/)
- Dashboard Stats
- Doctor Management
- Appointment Management
- User Profile
🔌 API و سرویسها
تنظیمات Axios (services/api.js)
const BASE_URL = process.env.NEXT_PUBLIC_API_URL;
const token = Cookies.get("access_token");
const api = axios.create({
baseURL: BASE_URL,
headers: {
"Content-Type": "application/json",
Authorization: token ? `Bearer ${token}` : "",
},
});
api.interceptors.response.use(
(response) => response.data,
(error) => Promise.reject(error)
);
API Endpoints (services/response.js)
دستهبندی Endpoints:
// Authentication
postLogin(data);
getUserInfo(headers);
// Doctors
getDoctors(params);
getDoctor(slug);
postDoctor(data);
getDoctorServices();
// Clinics
getClinics(params);
getClinic(slug);
getClinicDoctors(slug, params);
// Appointments
getAppointment(doctor_id, date);
postAppointment(data);
getAppointmentNotAvailable(doctor_id);
getAppointmentWeeklySchedule(uuid);
postAppointmentWeeklySchedule();
patchAppointmentWeeklySchedule(uuid);
deleteAppointmentWeeklySchedule(uuid);
// User Profile
getUserProfile(uuid);
postUserProfile(data);
patchUserProfile(data, uuid, token);
// Categories
getSpecialties();
getInsuranceType();
// File Upload
postImageUpload(data);
Helper Functions (helper/index.js)
// تبدیل اعداد به فرمت عربی
export const numberToArStyle = (num) => num?.toLocaleString("ar-AE") || "";
// تبدیل تاریخ میلادی به شمسی
export const convertToJalali = (date) => {
const jalaliDate = moment(date).format("jYYYY/jM/jD");
// ...
};
// بررسی URL فعال
export const isActiveURL = (link, name) => link === name;
// آپلود تصویر
export const uploadImage = async (event) => {
// ...
};
// ساخت Query String برای فیلتر پزشکان
export const buildDoctorParams = (searchParams) => {
// ...
};
🎨 استایلدهی
Tailwind CSS
پروژه از Tailwind CSS برای استایلدهی استفاده میکند:
// tailwind.config.js
module.exports = {
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
darkMode: "class",
theme: {
extend: {
// Custom colors, fonts, etc.
},
},
};
Material-UI Theme
// mui/index.js
const theme = createTheme({
direction: "rtl",
typography: {
fontFamily: "IRANSans, Arial",
},
palette: {
primary: { main: "#5559CE" },
// ...
},
});
Dark Mode
// app/Providers.js
export function Providers({ children }) {
const router = usePathname();
return (
<ThemeProvider
attribute={router.includes("/panel") ? "class" : "data-"}
defaultTheme="system"
enableSystem
>
{children}
</ThemeProvider>
);
}
🐳 Deployment
متغیرهای محیطی مورد نیاز
# API Configuration
NEXT_PUBLIC_API_URL=https://api.nobat724.com
NEXT_PUBLIC_CLIENT_ID=your_client_id
NEXT_PUBLIC_CLIENT_SECRET=your_client_secret
# Development Mode (TRUE/FALSE)
DEV_MODE=FALSE
# Application Port
APP_PORT=3000
# Node Environment
NODE_ENV=production
Build Production
# Build
npm run build
# Start Production Server
npm run start
Docker Deployment
# Build Image
docker build -t nobat724-front .
# Run Container
docker run -p 3000:3000 \
-e NEXT_PUBLIC_API_URL=https://api.nobat724.com \
-e DEV_MODE=FALSE \
nobat724-front
Docker Compose
# docker-compose.yml
version: "3.8"
services:
frontend:
build:
context: .
dockerfile: Dockerfile
restart: unless-stopped
ports:
- "${APP_PORT}:3000"
environment:
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
- DEV_MODE=${DEV_MODE}
📝 نکات توسعه
1. افزودن صفحه جدید
// app/new-page/page.js
import Layout from "@/components/layout/StLayout";
export default function NewPage() {
return (
<Layout>
<div>محتوای صفحه جدید</div>
</Layout>
);
}
2. افزودن API Endpoint جدید
// services/response.js
export const request = {
// ...
getNewData: () => api.get("api/v1/new-endpoint"),
postNewData: (data) => api.post("api/v1/new-endpoint", data),
};
3. افزودن کامپوننت جدید
// components/NewComponent/index.js
"use client"; // اگر Client Component است
function NewComponent({ data }) {
return <div>{/* محتوای کامپوننت */}</div>;
}
export default NewComponent;
4. کار با تاریخ شمسی
import moment from "jalali-moment";
// تبدیل میلادی به شمسی
const jalaliDate = moment("2024-01-15").format("jYYYY/jMM/jDD");
// تبدیل شمسی به میلادی
const gregorianDate = moment("1402/10/25", "jYYYY/jMM/jDD").format(
"YYYY-MM-DD"
);
5. استفاده از Context
"use client";
import { useProvince } from "@/context/ProvinceProvider";
function MyComponent() {
const { isProvinceInclude } = useProvince();
return <div>{isProvinceInclude && <p>شهر: {isProvinceInclude.name}</p>}</div>;
}
🔧 Troubleshooting
مشکلات رایج
1. خطای Hydration
Error: Text content does not match server-rendered HTML
راهحل: استفاده از useEffect برای محتوای Client-Side
2. خطای Authentication
Unauthorized (401)
راهحل: بررسی توکن در Cookie و تنظیم Header
3. مشکل Dark Mode
Flash of unstyled content
راهحل: استفاده صحیح از next-themes و ThemeProvider
📚 منابع مفید
- Next.js Documentation
- Material-UI Documentation
- Tailwind CSS Documentation
- CASL Documentation
- Jalali Moment
👥 تیم توسعه
Senior Frontend Developer: شما! 🎉
Tech Stack Expertise:
- ✅ Next.js 14 (App Router)
- ✅ React.js 18
- ✅ Material-UI v7
- ✅ Tailwind CSS
- ✅ Server Components & Client Components
- ✅ Authentication & Authorization
- ✅ Multi-Domain Architecture
- ✅ Persian/Jalali Date Handling
📞 پشتیبانی
برای هرگونه سوال یا مشکل:
- 📧 Email: nobat724@gmail.com
- 🌐 Website: https://nobat724.com
تاریخ آخرین بهروزرسانی: نوامبر 2025
نسخه پروژه: 0.1.0