diff --git a/assets/admin/App.tsx b/assets/admin/App.tsx index 381eec00..11c87a1f 100644 --- a/assets/admin/App.tsx +++ b/assets/admin/App.tsx @@ -29,6 +29,10 @@ import SecretariesPage from './pages/SecretariesPage'; import MyClinicPage from './pages/MyClinicPage'; import SettingsPage from './pages/SettingsPage'; import DoctorProfilePage from './pages/DoctorProfilePage'; +import MyPatientsPage from './pages/MyPatientsPage'; +import MyFinancialPage from './pages/MyFinancialPage'; +import ClinicFormPage from './pages/ClinicFormPage'; +import PreRegistrationsPage from './pages/PreRegistrationsPage'; // ── Guards ────────────────────────────────────────────────────────────────── @@ -134,6 +138,14 @@ export default function App() { } /> } /> } /> + + {/* دکتر / منشی / کلینیک */} + } /> + } /> + + {/* فقط ادمین */} + } /> + } /> } /> diff --git a/assets/admin/components/layout/Sidebar.tsx b/assets/admin/components/layout/Sidebar.tsx index 52ba7e7d..9c5daa91 100644 --- a/assets/admin/components/layout/Sidebar.tsx +++ b/assets/admin/components/layout/Sidebar.tsx @@ -3,6 +3,7 @@ import { ArrowsRightLeftIcon, BanknotesIcon, BuildingOffice2Icon, + ClipboardDocumentCheckIcon, Cog6ToothIcon, CalendarDaysIcon, ChartBarIcon, @@ -43,6 +44,7 @@ function buildSections(primaryRole: string | null, dbUuid: string | null): Secti { to: '/admin/appointments', icon: CalendarDaysIcon, label: 'نوبت‌ها' }, { to: '/admin/payments', icon: CreditCardIcon, label: 'پرداخت‌ها' }, { to: '/admin/settlements', icon: BanknotesIcon, label: 'تسویه‌حساب' }, + { to: '/admin/pre-registrations', icon: ClipboardDocumentCheckIcon, label: 'درخواست‌های ثبت‌نام' }, ], }, { diff --git a/assets/admin/pages/ClinicFormPage.tsx b/assets/admin/pages/ClinicFormPage.tsx new file mode 100644 index 00000000..a2db580d --- /dev/null +++ b/assets/admin/pages/ClinicFormPage.tsx @@ -0,0 +1,83 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useMutation } from '@tanstack/react-query'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { ArrowRightIcon, BuildingOffice2Icon } from '@heroicons/react/24/outline'; +import { toast } from 'sonner'; +import { api } from '../lib/api'; +import type { ApiResponse } from '../lib/api'; + +const schema = z.object({ + owner_mobile: z.string().min(10, 'شماره موبایل معتبر نیست').max(15), + name: z.string().min(2, 'نام کلینیک حداقل ۲ کاراکتر'), + telephone: z.string().max(20).optional().or(z.literal('')), + address: z.string().max(500).optional().or(z.literal('')), + info: z.string().max(2000).optional().or(z.literal('')), +}); +type FormValues = z.infer; + +interface ClinicCreated { uuid: string; name: string; is_active: boolean } + +export default function ClinicFormPage() { + const navigate = useNavigate(); + const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm({ resolver: zodResolver(schema) }); + + const mutation = useMutation, Error, FormValues>({ + mutationFn: (body) => api.post('/api/v1/admin/clinic', body), + onSuccess: (res) => { + toast.success(`کلینیک "${res?.data?.name}" ایجاد شد`); + navigate('/admin/clinics'); + }, + onError: (err) => toast.error(err.message ?? 'خطا در ایجاد کلینیک'), + }); + + return ( +
+
+ +
+ +

افزودن کلینیک جدید

+
+
+ +
+
mutation.mutate(v))} style={{ display: 'flex', flexDirection: 'column', gap: 20 }}> +
+ + + {errors.owner_mobile && {errors.owner_mobile.message}} + اگر این موبایل در سیستم نباشد، کاربر جدید ساخته می‌شود +
+
+ + + {errors.name && {errors.name.message}} +
+
+ + +
+
+ + +
+
+ + +
+
- var navToggle = document.getElementById('navToggle'); - var navLinks = document.getElementById('navLinks'); - if (navToggle) navToggle.addEventListener('click', function(){ - var open = navLinks.style.display === 'flex'; - navLinks.style.display = open ? '' : 'flex'; - if (!open) { - navLinks.style.position='absolute'; navLinks.style.top='84px'; navLinks.style.insetInline='0'; - navLinks.style.flexDirection='column'; navLinks.style.background='#fff'; - navLinks.style.padding='20px 32px'; navLinks.style.gap='18px'; - navLinks.style.boxShadow='0 16px 30px oklch(0.4 0.08 285 / 0.1)'; - } - }); + + - var sections = ['hero','features','why','download','contact']; - var links = Array.prototype.slice.call(document.querySelectorAll('.nav-links a')); - function syncActive(){ - var y = window.scrollY + 120, cur = 'hero'; - sections.forEach(function(id){ var el=document.getElementById(id); if(el && el.offsetTop<=y) cur=id; }); - links.forEach(function(a){ a.classList.toggle('active', a.getAttribute('href')==='#'+cur); }); - } - window.addEventListener('scroll', syncActive, { passive: true }); - syncActive(); + +

پس از بررسی، اطلاعات ورود از طریق SMS ارسال می‌شود

+
+ - var reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches; - var reveals = Array.prototype.slice.call(document.querySelectorAll('.reveal')); - function inView(el){ var r=el.getBoundingClientRect(); return r.top < (window.innerHeight||document.documentElement.clientHeight)-30 && r.bottom > 0; } - function reveal(el){ el.classList.add('in'); } + - if (reduce) { reveals.forEach(reveal); } - else { - reveals.forEach(function(el){ if(inView(el)) reveal(el); }); - if ('IntersectionObserver' in window) { - var io = new IntersectionObserver(function(es){ es.forEach(function(e){ if(e.isIntersecting){ reveal(e.target); io.unobserve(e.target);} }); }, { threshold: 0.12, rootMargin: '0px 0px -40px 0px' }); - reveals.forEach(function(el){ if(!el.classList.contains('in')) io.observe(el); }); - } - var ticking=false; - function sweep(){ if(ticking) return; ticking=true; requestAnimationFrame(function(){ reveals.forEach(function(el){ if(inView(el)) reveal(el); }); ticking=false; }); } - window.addEventListener('scroll', sweep, { passive: true }); - window.addEventListener('resize', sweep, { passive: true }); - setTimeout(function(){ reveals.forEach(reveal); }, 2800); - } + + -{{ encore_entry_script_tags('home') }} - + + + + + + + + {{ encore_entry_script_tags('home') }} +