Files
clinicpro/assets/admin/pages/SecretaryDetailPage.tsx
T
hamed e0e8fbd1e4 feat: implement BackButton component for consistent navigation
- Added BackButton component to standardize back navigation across pages.
- Integrated BackButton into various pages, replacing custom back buttons for consistency.
- Updated PageHeader to accept backTo prop for displaying BackButton when navigating from subpages.
- Created useGoBack hook to handle navigation logic, determining whether to go back in history or redirect to a fallback page.
- Added tests for BackButton and its integration with PageHeader to ensure expected behavior.
2026-07-29 20:26:51 +03:30

145 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Link, useParams } from 'react-router-dom';
import { ChevronRightIcon } from '@heroicons/react/24/outline';
import { toast } from 'sonner';
import { api } from '../lib/api';
import type { ApiResponse } from '../lib/api';
import type { Secretary } from '../types';
import { digitsOnly, formatDate, formatNumber, formatRial } from '../lib/utils';
import PageHeader from '../components/ui/PageHeader';
/** یک ردیف label:value با همان تم کارت‌های موجود. */
function Row({ label, value }: { label: string; value: React.ReactNode }) {
return (
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 10, padding: '9px 0', borderBottom: '1px solid var(--border)' }}>
<span style={{ fontSize: 12.5, color: 'var(--text-3)' }}>{label}</span>
<span style={{ fontSize: 13.5, color: 'var(--text)', fontWeight: 500 }}>{value}</span>
</div>
);
}
/**
* جزئیات یک رابطهٔ منشی–پزشک/کلینیک برای ادمین: مشخصات، سهم درآمد نوبت‌های آنلاین
* (فعال‌سازی + درصد) و خلاصهٔ درآمد.
*/
export default function SecretaryDetailPage() {
const { uuid = '' } = useParams();
const qc = useQueryClient();
const [enabled, setEnabled] = useState(false);
const [percent, setPercent] = useState('0');
const { data, isLoading } = useQuery<ApiResponse<{ data: Secretary }>>({
queryKey: ['admin-secretary', uuid],
queryFn: () => api.get(`/api/v1/admin/secretary/${uuid}`),
enabled: !!uuid,
});
const secretary = data?.data?.data;
useEffect(() => {
if (!secretary) return;
setEnabled(!!secretary.online_share_enabled);
setPercent(String(secretary.online_share_percent ?? 0));
}, [data]);
const save = useMutation({
mutationFn: () => api.put(`/api/v1/admin/secretary/${uuid}/online-share`, {
enabled,
percent: Number(percent) || 0,
}),
onSuccess: () => {
toast.success('سهم درآمد منشی ذخیره شد');
qc.invalidateQueries({ queryKey: ['admin-secretary', uuid] });
qc.invalidateQueries({ queryKey: ['secretaries'] });
},
onError: (e: Error) => toast.error(e.message),
});
const invalidPercent = Number(percent) > 100 || (enabled && Number(percent) <= 0);
if (isLoading || !secretary) {
return <div style={{ padding: 24, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>;
}
return (
<div className="fade-in">
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 14 }}>
<Link to="/admin/secretaries" className="btn sm ghost" style={{ color: 'var(--text-2)' }}>
<ChevronRightIcon style={{ width: 16 }} /> بازگشت
</Link>
</div>
<PageHeader backTo="/admin/secretaries" title={secretary.user_name} description="جزئیات منشی و سهم درآمد نوبت‌های آنلاین" />
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(300px, 1fr))', gap: 'var(--gap)' }}>
<div className="card" style={{ padding: 20 }}>
<h2 style={{ fontSize: 15, fontWeight: 700, margin: '0 0 10px' }}>اطلاعات منشی</h2>
<Row label="نام" value={secretary.user_name} />
<Row label="موبایل" value={<span dir="ltr">{secretary.mobile_number}</span>} />
<Row label="پزشک" value={secretary.doctor_name} />
{secretary.clinic_name && <Row label="کلینیک" value={secretary.clinic_name} />}
<Row label="وضعیت" value={
<span style={{ color: secretary.is_active ? 'var(--success)' : 'var(--text-3)', fontWeight: 700 }}>
{secretary.is_active ? 'فعال' : 'غیرفعال'}
</span>
} />
<Row label="تاریخ ثبت" value={formatDate(secretary.created_at)} />
</div>
<div className="card" style={{ padding: 20 }}>
<h2 style={{ fontSize: 15, fontWeight: 700, margin: '0 0 6px' }}>سهم درآمد نوبت‌های آنلاین</h2>
<p style={{ margin: '0 0 14px', fontSize: 12, lineHeight: 1.9, color: 'var(--text-2)' }}>
درصد سهم از <b>مبلغ خالص</b> نوبت محاسبه می‌شود: ابتدا هزینهٔ پیامک و مالیات و
کسورات از مبلغ نوبت کم می‌شود، سپس این درصد اعمال می‌گردد. فقط نوبت‌هایی که
آنلاین ثبت و پرداخت می‌شوند سهم می‌سازند.
</p>
<label style={{ display: 'inline-flex', alignItems: 'center', gap: 10, cursor: 'pointer', marginBottom: 14 }}>
<span className="switch">
<input
type="checkbox"
aria-label="محاسبه درآمد منشی از نوبت‌های آنلاین"
checked={enabled}
onChange={(e) => setEnabled(e.target.checked)}
/>
<span className="switch-track"><span className="switch-thumb" /></span>
</span>
<span style={{ fontSize: 13 }}>محاسبه درآمد از نوبت‌های آنلاین فعال باشد</span>
</label>
<div className="form-row">
<label>درصد سهم منشی</label>
<input
className="input"
inputMode="numeric"
dir="ltr"
aria-label="درصد سهم منشی"
value={percent}
onChange={(e) => setPercent(digitsOnly(e.target.value, 3))}
placeholder="مثلاً: ۵"
/>
{Number(percent) > 100 && <p className="err-text">درصد نمی‌تواند بیشتر از ۱۰۰ باشد</p>}
{enabled && Number(percent) <= 0 && <p className="err-text">برای فعال‌سازی، درصد باید بیشتر از صفر باشد</p>}
</div>
<button
className="btn primary sm"
style={{ marginTop: 14 }}
disabled={save.isPending || invalidPercent}
onClick={() => save.mutate()}
>
{save.isPending ? 'در حال ذخیره...' : 'ذخیره'}
</button>
</div>
<div className="card" style={{ padding: 20 }}>
<h2 style={{ fontSize: 15, fontWeight: 700, margin: '0 0 10px' }}>خلاصه درآمد</h2>
<Row label="کل درآمد" value={formatRial(secretary.earnings?.total_rials ?? 0)} />
<Row label="۳۰ روز گذشته" value={formatRial(secretary.earnings?.this_month_rials ?? 0)} />
<Row label="تعداد نوبت" value={formatNumber(secretary.earnings?.appointments_count ?? 0)} />
</div>
</div>
</div>
);
}