Files
clinicpro/assets/admin/components/session/SessionAuditModal.tsx
T
hamed 55ab2f5dfc Implement comprehensive dark/light mode overhaul for admin panel
- Refactor color palette in `ui-design-spec.md` to utilize CSS variables exclusively, eliminating fixed hex values and Tailwind utility classes.
- Complete dark mode implementation in `uiStore.ts`, ensuring proper theme application via `applyTheme()` and `applyBrand()`.
- Create `admin-theme-dark-light-audit.md` to document the transition process, outlining issues with inline styles and fixed colors.
- Introduce `theme-tokens.test.ts` to enforce rules against fixed hex colors and ensure compliance with the design system.
- Update various components and styles to replace inline styles and fixed colors with CSS variables, ensuring consistent theming across light and dark modes.
- Ensure all changes maintain visual integrity in both light and dark modes, with a focus on accessibility and contrast standards.
2026-07-27 16:41:52 +03:30

78 lines
3.9 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { api } from '../../lib/api';
import type { ApiResponse } from '../../lib/api';
import type { SessionAuditEntry } from '../../types';
import { formatDateTime, formatRial } from '../../lib/utils';
import Modal from '../ui/Modal';
const FIELD_LABELS: Record<string, string> = {
visit_price_rials: 'قیمت ویزیت',
services: 'سرویس‌ها',
consumables: 'کالاهای مصرفی',
services_total_rials: 'مجموع سرویس‌ها',
final_price_rials: 'مبلغ نهایی',
payment: 'پرداخت',
discount: 'تخفیف',
};
const OP_LABELS: Record<string, { label: string; color: string }> = {
create: { label: 'ایجاد', color: 'var(--success)' },
update: { label: 'ویرایش', color: 'var(--primary)' },
delete: { label: 'حذف', color: 'var(--danger)' },
};
// مقادیر پولی (ریال) را با فرمت تومان نشان بده؛ بقیه را خام.
const RIAL_FIELDS = new Set(['visit_price_rials', 'services_total_rials', 'final_price_rials', 'payment', 'discount']);
const fmtValue = (field: string, v: string | null): string => {
if (v === null || v === '') return '—';
if (RIAL_FIELDS.has(field) && /^\d+$/.test(v)) return formatRial(Number(v));
return v;
};
export default function SessionAuditModal({ sessionUuid, onClose }: { sessionUuid: string | null; onClose: () => void }) {
const { data, isLoading } = useQuery({
queryKey: ['session-audit-log', sessionUuid],
queryFn: () => api.get<ApiResponse<SessionAuditEntry[]>>(`/api/v1/session/${sessionUuid}/audit-log`),
enabled: !!sessionUuid,
});
const rows: SessionAuditEntry[] = (data?.data as any) ?? [];
if (!sessionUuid) return null;
return (
<Modal open title="تاریخچه تغییرات" size="lg" onClose={onClose}>
{isLoading ? (
<div style={{ padding: 16, color: 'var(--text-3)', fontSize: 13 }}>در حال بارگذاری...</div>
) : rows.length === 0 ? (
<div style={{ padding: 24, textAlign: 'center', color: 'var(--text-3)', fontSize: 14 }}>تغییری ثبت نشده است.</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{rows.map((r, i) => {
const op = OP_LABELS[r.operation] ?? { label: r.operation, color: 'var(--text-3)' };
return (
<div key={i} className="card" style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 6 }}>
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
<span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<span className="badge" style={{ fontSize: 11, color: 'var(--on-primary)', background: op.color, borderRadius: 4, padding: '2px 8px' }}>{op.label}</span>
<span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{FIELD_LABELS[r.field] ?? r.field}</span>
</span>
<span style={{ fontSize: 12, color: 'var(--text-3)' }}>{formatDateTime(r.created_at)}</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, flexWrap: 'wrap' }}>
<span style={{ color: 'var(--text-3)' }}>{fmtValue(r.field, r.old_value)}</span>
<span style={{ color: 'var(--text-3)' }}></span>
<span style={{ color: 'var(--text)', fontWeight: 600 }}>{fmtValue(r.field, r.new_value)}</span>
</div>
<div style={{ display: 'flex', gap: 12, fontSize: 12, color: 'var(--text-3)' }}>
{r.actor_name && <span>توسط: {r.actor_name}</span>}
{r.note && <span>{r.note}</span>}
</div>
</div>
);
})}
</div>
)}
</Modal>
);
}