hamedandClaude Opus 4.8 cf705a894e feat(appointment): inline dual-month Jalali calendar for day selection
The day picker used a Popover-based JalaliDatePicker that only showed two
empty text inputs on the booking page — the calendar was hidden until you
clicked the input, so it never matched the design. Replace it with an
always-visible inline two-month calendar (InlineJalaliMonth) rendered side
by side: current month on the right, next on the left (RTL), weekday
headers, Fridays in red, disabled days greyed, selected day in orange,
shared month navigation on the outer edges. Preserves the existing
contract: setDate(unix timestamp), disabledDates[], auto-select nearest
available day.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 15:58:23 +03:30
2025-01-12 15:15:48 +03:30
2025-05-18 01:18:35 +03:30
2024-10-25 22:16:00 +03:30
2025-10-04 20:36:50 +03:30
2024-12-15 10:48:29 +03:30
2025-06-30 11:10:53 +03:30

🏥 نوبت724 (Nobat724) - سیستم نوبت‌دهی آنلاین پزشکی

📋 فهرست مطالب


🎯 معرفی پروژه

نوبت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. سیستم رزرو نوبت

فرآیند رزرو نوبت شامل مراحل زیر است:

  1. انتخاب تاریخ و زمان (components/appointment/date/)
  2. ورود یا ثبت‌نام (در صورت نیاز)
  3. وارد کردن اطلاعات بیمار (components/appointment/detail/)
  4. پرداخت (components/appointment/paying/)
  5. تایید نهایی (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 : "کلمات کلیدی پیش‌فرض",
  };
}

🔐 احراز هویت و مجوزها

فرآیند ورود

  1. ورود شماره موبایل (/login)
  2. دریافت کد OTP
  3. تایید کد (/login-verify)
  4. ذخیره توکن‌ها در Cookie
  5. Redirect به Dashboard یا Panel

Cookies استفاده شده

  • access_token: JWT Token
  • refresh_token: Refresh Token
  • uuid: شناسه کاربر
  • 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


📚 منابع مفید


👥 تیم توسعه

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

📞 پشتیبانی

برای هرگونه سوال یا مشکل:


تاریخ آخرین به‌روزرسانی: نوامبر 2025

نسخه پروژه: 0.1.0

S
Description
No description provided
Readme
28 MiB
Languages
JavaScript 80.9%
HTML 15.5%
CSS 3.3%
Dockerfile 0.3%