feat(admin): force Latin digits in numeric fields globally

- Add a numeric prop to the base Input component that sets inputMode,
  dir=ltr, lang=en and normalizes Persian/Arabic digits to Latin via the
  shared toEnglishDigits on every change.
- Dedupe digit conversion: PriceInput now uses toEnglishDigits instead of
  its local map; AppointmentsPage mobile handler uses sanitizeMobileInput.
- Fix raw national-code / mobile inputs in NewAppointmentModal and
  NewAppointmentDrawer that stripped Persian digits without converting.
- Cover the numeric Input behaviour with unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-17 10:58:55 +03:30
co-authored by Claude Fable 5
parent 2638b70162
commit 15f1c8d1eb
5 changed files with 54 additions and 27 deletions
+27 -2
View File
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import Input from '@/components/ui/Input';
import Field from '@/components/ui/Field';
@@ -16,6 +16,31 @@ describe('Input', () => {
const el = screen.getByPlaceholderText('کدملی') as HTMLInputElement;
expect(el.style.borderColor).toBe('var(--danger)');
});
it('با numeric کیبورد عددی و جهت LTR می‌گیرد', () => {
render(<Input placeholder="مبلغ" numeric />);
const el = screen.getByPlaceholderText('مبلغ') as HTMLInputElement;
expect(el.inputMode).toBe('numeric');
expect(el.getAttribute('dir')).toBe('ltr');
expect(el.getAttribute('lang')).toBe('en');
});
it('با numeric ارقام فارسی را قبل از onChange به لاتین تبدیل می‌کند', () => {
const onChange = vi.fn();
render(<Input placeholder="مبلغ" numeric onChange={onChange} />);
const el = screen.getByPlaceholderText('مبلغ') as HTMLInputElement;
fireEvent.change(el, { target: { value: '۱۲۳۴' } });
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange.mock.calls[0][0].target.value).toBe('1234');
});
it('بدون numeric ورودی را دست‌نخورده می‌گذارد', () => {
const onChange = vi.fn();
render(<Input placeholder="نام" onChange={onChange} />);
const el = screen.getByPlaceholderText('نام') as HTMLInputElement;
fireEvent.change(el, { target: { value: 'علی۱۲' } });
expect(onChange.mock.calls[0][0].target.value).toBe('علی۱۲');
});
});
describe('Field', () => {