feat(api): add dashboard endpoints for clinic, doctor, and secretary roles
- Implemented GET /api/v1/dashboard/clinic to return clinic stats and today's schedule for clinic owners. - Implemented GET /api/v1/dashboard/doctor to return doctor's stats and today's schedule for doctors. - Implemented GET /api/v1/dashboard/secretary to return stats and conditional appointments for secretaries. feat(migrations): create user_active_context and mobile_verification_otp tables - Added migration to create user_active_context table for tracking active user sessions. - Added migration to create mobile_verification_otp table for handling mobile number verification. feat(migrations): create site_config table for application settings - Added migration to create site_config table to store various site configuration settings. feat(appointments): create MyAppointmentsController for user-specific appointments - Added MyAppointmentsController to handle fetching user-specific appointments with pagination and filtering. feat(auth): implement NotificationMobileController for mobile number verification - Added NotificationMobileController to handle OTP requests and verification for mobile number changes. feat(auth): create MobileVerificationOtp entity for OTP management - Created MobileVerificationOtp entity to manage OTP records for mobile verification. feat(auth): create UserActiveContext entity for user session management - Created UserActiveContext entity to manage user active sessions. feat(config): implement SiteConfigController for managing site settings - Added SiteConfigController to handle fetching and updating site configuration settings. feat(config): create SiteConfig entity and repository for configuration management - Created SiteConfig entity and repository to manage site configuration data.
This commit is contained in:
+77
-50
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { Routes, Route, Navigate } from 'react-router-dom';
|
||||
import { useAuthStore } from './stores/authStore';
|
||||
import AdminLayout from './components/layout/AdminLayout';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import DashboardPage from './pages/DashboardPage';
|
||||
import SelectContextPage from './pages/SelectContextPage';
|
||||
import UsersPage from './pages/UsersPage';
|
||||
import UserDetailPage from './pages/UserDetailPage';
|
||||
import DoctorsPage from './pages/DoctorsPage';
|
||||
@@ -25,10 +26,33 @@ import CategoriesPage from './pages/CategoriesPage';
|
||||
import BlogsPage from './pages/BlogsPage';
|
||||
import BlogFormPage from './pages/BlogFormPage';
|
||||
import SecretariesPage from './pages/SecretariesPage';
|
||||
import MyClinicPage from './pages/MyClinicPage';
|
||||
import SettingsPage from './pages/SettingsPage';
|
||||
|
||||
// ── Guards ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function PrivateRoute({ children }: { children: React.ReactNode }) {
|
||||
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
|
||||
return isAuthenticated ? <>{children}</> : <Navigate to="/admin/login" replace />;
|
||||
const { isAuthenticated, primaryRole, availableContexts, dbUuid, fetchMe } = useAuthStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated && !primaryRole) {
|
||||
fetchMe();
|
||||
}
|
||||
}, [isAuthenticated, primaryRole, fetchMe]);
|
||||
|
||||
if (!isAuthenticated) return <Navigate to="/admin/login" replace />;
|
||||
|
||||
// اگر context هنوز لود نشده — صبر کن
|
||||
if (isAuthenticated && !primaryRole) {
|
||||
return <div style={{ padding: 40, textAlign: 'center' }}>در حال بارگذاری...</div>;
|
||||
}
|
||||
|
||||
// اگر چند context دارد و هنوز انتخاب نشده — به صفحه انتخاب برو
|
||||
if (availableContexts.length > 1 && !dbUuid) {
|
||||
return <Navigate to="/admin/select-context" replace />;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
function PublicRoute({ children }: { children: React.ReactNode }) {
|
||||
@@ -36,74 +60,77 @@ function PublicRoute({ children }: { children: React.ReactNode }) {
|
||||
return isAuthenticated ? <Navigate to="/admin/dashboard" replace /> : <>{children}</>;
|
||||
}
|
||||
|
||||
function RoleRoute({ roles, children }: { roles: string[]; children: React.ReactNode }) {
|
||||
const primaryRole = useAuthStore((s) => s.primaryRole);
|
||||
if (!primaryRole) return <div style={{ padding: 40, textAlign: 'center' }}>در حال بارگذاری...</div>;
|
||||
if (!roles.includes(primaryRole)) return <Navigate to="/admin/dashboard" replace />;
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// ── App ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<Routes>
|
||||
{/* Public */}
|
||||
<Route
|
||||
path="/admin/login"
|
||||
element={
|
||||
<PublicRoute>
|
||||
<LoginPage />
|
||||
</PublicRoute>
|
||||
}
|
||||
element={<PublicRoute><LoginPage /></PublicRoute>}
|
||||
/>
|
||||
|
||||
{/* انتخاب محیط کاری — نیاز به auth دارد اما خارج از AdminLayout */}
|
||||
<Route
|
||||
path="/admin/*"
|
||||
path="/admin/select-context"
|
||||
element={
|
||||
<PrivateRoute>
|
||||
<AdminLayout />
|
||||
<SelectContextPage />
|
||||
</PrivateRoute>
|
||||
}
|
||||
/>
|
||||
|
||||
{/* Protected */}
|
||||
<Route
|
||||
path="/admin/*"
|
||||
element={<PrivateRoute><AdminLayout /></PrivateRoute>}
|
||||
>
|
||||
<Route index element={<Navigate to="/admin/dashboard" replace />} />
|
||||
|
||||
{/* داشبورد — همه نقشها */}
|
||||
<Route path="dashboard" element={<DashboardPage />} />
|
||||
|
||||
{/* Users */}
|
||||
<Route path="users" element={<UsersPage />} />
|
||||
<Route path="users/:uuid" element={<UserDetailPage />} />
|
||||
|
||||
{/* Doctors */}
|
||||
<Route path="doctors" element={<DoctorsPage />} />
|
||||
<Route path="doctors/new" element={<DoctorFormPage />} />
|
||||
<Route path="doctors/:uuid" element={<DoctorDetailPage />} />
|
||||
|
||||
{/* Clinics */}
|
||||
<Route path="clinics" element={<ClinicsPage />} />
|
||||
<Route path="clinics/:uuid" element={<ClinicDetailPage />} />
|
||||
|
||||
{/* Appointments */}
|
||||
{/* نوبتها — همه نقشها */}
|
||||
<Route path="appointments" element={<AppointmentsPage />} />
|
||||
<Route path="appointments/:uuid" element={<AppointmentDetailPage />} />
|
||||
|
||||
{/* Payments */}
|
||||
<Route path="payments" element={<PaymentsPage />} />
|
||||
<Route path="payments/:uuid" element={<PaymentDetailPage />} />
|
||||
{/* فقط ادمین */}
|
||||
<Route path="users" element={<RoleRoute roles={['admin']}><UsersPage /></RoleRoute>} />
|
||||
<Route path="users/:uuid" element={<RoleRoute roles={['admin']}><UserDetailPage /></RoleRoute>} />
|
||||
<Route path="payments" element={<RoleRoute roles={['admin']}><PaymentsPage /></RoleRoute>} />
|
||||
<Route path="payments/:uuid" element={<RoleRoute roles={['admin']}><PaymentDetailPage /></RoleRoute>} />
|
||||
<Route path="settlements" element={<RoleRoute roles={['admin']}><SettlementsPage /></RoleRoute>} />
|
||||
<Route path="representations" element={<RoleRoute roles={['admin']}><RepresentationsPage /></RoleRoute>} />
|
||||
<Route path="representations/:uuid" element={<RoleRoute roles={['admin']}><RepresentationDetailPage /></RoleRoute>} />
|
||||
<Route path="comments" element={<RoleRoute roles={['admin']}><CommentsPage /></RoleRoute>} />
|
||||
<Route path="ratings" element={<RoleRoute roles={['admin']}><RatingsPage /></RoleRoute>} />
|
||||
<Route path="sms" element={<RoleRoute roles={['admin']}><SmsPage /></RoleRoute>} />
|
||||
<Route path="categories" element={<RoleRoute roles={['admin']}><CategoriesPage /></RoleRoute>} />
|
||||
<Route path="blogs" element={<RoleRoute roles={['admin']}><BlogsPage /></RoleRoute>} />
|
||||
<Route path="blogs/new" element={<RoleRoute roles={['admin']}><BlogFormPage /></RoleRoute>} />
|
||||
<Route path="blogs/:uuid/edit" element={<RoleRoute roles={['admin']}><BlogFormPage /></RoleRoute>} />
|
||||
<Route path="secretaries" element={<RoleRoute roles={['admin']}><SecretariesPage /></RoleRoute>} />
|
||||
<Route path="clinics" element={<RoleRoute roles={['admin']}><ClinicsPage /></RoleRoute>} />
|
||||
<Route path="settings" element={<RoleRoute roles={['admin']}><SettingsPage /></RoleRoute>} />
|
||||
|
||||
{/* Settlements */}
|
||||
<Route path="settlements" element={<SettlementsPage />} />
|
||||
{/* کلینیک من — fallback اگر dbUuid هنوز لود نشده */}
|
||||
<Route path="my-clinic" element={<RoleRoute roles={['clinic']}><MyClinicPage /></RoleRoute>} />
|
||||
|
||||
{/* Representations */}
|
||||
<Route path="representations" element={<RepresentationsPage />} />
|
||||
<Route path="representations/:uuid" element={<RepresentationDetailPage />} />
|
||||
|
||||
{/* Comments & Ratings */}
|
||||
<Route path="comments" element={<CommentsPage />} />
|
||||
<Route path="ratings" element={<RatingsPage />} />
|
||||
|
||||
{/* SMS */}
|
||||
<Route path="sms" element={<SmsPage />} />
|
||||
|
||||
{/* Categories */}
|
||||
<Route path="categories" element={<CategoriesPage />} />
|
||||
|
||||
{/* Blogs */}
|
||||
<Route path="blogs" element={<BlogsPage />} />
|
||||
<Route path="blogs/new" element={<BlogFormPage />} />
|
||||
<Route path="blogs/:uuid/edit" element={<BlogFormPage />} />
|
||||
|
||||
{/* Secretaries */}
|
||||
<Route path="secretaries" element={<SecretariesPage />} />
|
||||
{/* ادمین + کلینیک */}
|
||||
<Route path="clinics/:uuid" element={<RoleRoute roles={['admin', 'clinic']}><ClinicDetailPage /></RoleRoute>} />
|
||||
<Route path="doctors" element={<RoleRoute roles={['admin', 'clinic']}><DoctorsPage /></RoleRoute>} />
|
||||
<Route path="doctors/new" element={<RoleRoute roles={['admin', 'clinic']}><DoctorFormPage /></RoleRoute>} />
|
||||
<Route path="doctors/:uuid" element={<RoleRoute roles={['admin', 'clinic', 'doctor']}><DoctorDetailPage /></RoleRoute>} />
|
||||
</Route>
|
||||
|
||||
<Route path="*" element={<Navigate to="/admin/dashboard" replace />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user