- 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.
78 lines
3.9 KiB
TypeScript
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>
|
|
);
|
|
}
|