hamedandClaude Opus 5 15edc628e4 docs: describe the two booking modes and their front-end contract
Records what the service flow actually guarantees: mode is per location (a doctor
can be slot-based in their office and service-based in a clinic), duration is
server data and must never be summed in the front, and shift boundaries are not
derived client-side because a flat start_times list cannot tell a break between
shifts from a gap left by a booked appointment.

Also notes that user-panel reads of service fields are guarded, since slot-mode
appointments carry none of them.

Task: clinicpro/docs/new_feture/taskes/task-00b-nobat724-service-mode/

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 15:59:17 +03:30
2025-01-12 15:15:48 +03:30
2024-10-25 22:16:00 +03:30
2024-12-15 10:48:29 +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
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

# 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%