food-market/src/food-market.web/src/pages/SuperAdminOrgCreatePage.tsx
nurdotnet 2301446b06
Some checks failed
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 1m1s
CI / Web (React + Vite) (push) Failing after 37s
Docker Public / Build + push Public (push) Successful in 27s
Docker Web / Build + push Web (push) Failing after 25s
Docker Web / Deploy Web on stage (push) Has been skipped
Docker Public / Deploy Public on stage (push) Successful in 10s
feat(phone): единый PhoneInput с зашитым «+7» и ФЛК Казахстана
Поле телефона во всех формах (web + public) теперь использует общий компонент:
- Префикс «+7 » всегда виден и не удаляется (Backspace/Delete на позиции ≤3 блокируется).
- Принимаются только цифры (буквы и спецсимволы автоматически отфильтровываются).
- Авто-форматирование при вводе: «+7 7XX XXX XX XX».
- Paste произвольного формата нормализуется (поддерживается ведущая «8», «+7…», скобки, дефисы).
- Наружу через onChange отдаётся каноничное «+7XXXXXXXXXX».

Подключено в:
- food-market.public/SignupForm
- food-market.web/CounterpartiesPage (контрагенты)
- food-market.web/EmployeesPage (сотрудники)
- food-market.web/SuperAdminOrgEmployeesPage (управление сотрудниками SuperAdmin)
- food-market.web/SuperAdminOrgCreatePage (создание организации SuperAdmin)
2026-05-03 11:01:55 +05:00

144 lines
7.4 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 { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { Save, Copy } from 'lucide-react'
import { api } from '@/lib/api'
import { Button } from '@/components/Button'
import { Modal } from '@/components/Modal'
import { Field, TextInput, Select } from '@/components/Field'
import { PhoneInput } from '@/components/PhoneInput'
import { useCountries, useCurrencies } from '@/lib/useLookups'
import { PageHeader } from '@/components/PageHeader'
export function SuperAdminOrgCreatePage() {
const navigate = useNavigate()
const countries = useCountries()
const currencies = useCurrencies()
const [name, setName] = useState('')
const [countryCode, setCountryCode] = useState('KZ')
const [bin, setBin] = useState('')
const [address, setAddress] = useState('')
const [phone, setPhone] = useState('')
const [email, setEmail] = useState('')
const [defaultCurrencyId, setDefaultCurrencyId] = useState('')
const [adminLast, setAdminLast] = useState('')
const [adminFirst, setAdminFirst] = useState('')
const [adminEmail, setAdminEmail] = useState('')
const [adminPosition, setAdminPosition] = useState('Директор')
const [busy, setBusy] = useState(false)
const [error, setError] = useState<string | null>(null)
const [done, setDone] = useState<{ email: string; password: string } | null>(null)
const onCountryChange = (cc: string) => {
setCountryCode(cc)
const c = countries.data?.find((x) => x.code === cc)
if (c?.defaultCurrencyId) setDefaultCurrencyId(c.defaultCurrencyId)
}
const submit = async () => {
setError(null); setBusy(true)
try {
const res = await api.post<{ adminEmail: string; adminTempPassword: string; organization: { id: string } }>(
'/api/super-admin/organizations',
{
org: { name, countryCode, bin: bin || null, address: address || null,
phone: phone || null, email: email || null,
defaultCurrencyId: defaultCurrencyId || null, accountOwnerUserId: null },
adminLastName: adminLast, adminFirstName: adminFirst,
adminEmail, adminPosition: adminPosition || null,
})
setDone({ email: res.data.adminEmail, password: res.data.adminTempPassword })
} catch (e) {
const msg = (e as { response?: { data?: { error?: string } } }).response?.data?.error ?? (e as Error).message
setError(msg)
} finally { setBusy(false) }
}
const canSubmit = name.trim() && countryCode && adminLast.trim() && adminFirst.trim() && adminEmail.trim()
return (
<div className="h-full overflow-auto">
<div className="max-w-2xl mx-auto p-4 sm:p-6 space-y-5">
<PageHeader title="Новая организация" description="Создаётся вместе с Администратором (AppUser + Employee запись)." />
{error && <div className="p-3 rounded-md bg-red-50 text-red-700 text-sm border border-red-200">{error}</div>}
<section className="rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-5 space-y-3">
<h3 className="text-sm font-semibold">Реквизиты организации</h3>
<Field label="Название *">
<TextInput value={name} onChange={(e) => setName(e.target.value)} />
</Field>
<div className="grid grid-cols-2 gap-3">
<Field label="Страна *">
<Select value={countryCode} onChange={(e) => onCountryChange(e.target.value)}>
{countries.data?.map((c) => <option key={c.code} value={c.code}>{c.name}</option>)}
</Select>
</Field>
<Field label="Валюта по умолчанию *">
<Select value={defaultCurrencyId} onChange={(e) => setDefaultCurrencyId(e.target.value)}>
<option value=""></option>
{currencies.data?.map((c) => <option key={c.id} value={c.id}>{c.code} ({c.symbol})</option>)}
</Select>
</Field>
</div>
<div className="grid grid-cols-2 gap-3">
<Field label="БИН/ИНН"><TextInput value={bin} onChange={(e) => setBin(e.target.value)} /></Field>
<Field label="Телефон"><PhoneInput value={phone} onChange={setPhone} /></Field>
</div>
<Field label="Адрес"><TextInput value={address} onChange={(e) => setAddress(e.target.value)} /></Field>
<Field label="Email организации"><TextInput type="email" value={email} onChange={(e) => setEmail(e.target.value)} /></Field>
</section>
<section className="rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-5 space-y-3">
<h3 className="text-sm font-semibold">Главный администратор</h3>
<div className="grid grid-cols-2 gap-3">
<Field label="Фамилия *"><TextInput value={adminLast} onChange={(e) => setAdminLast(e.target.value)} /></Field>
<Field label="Имя *"><TextInput value={adminFirst} onChange={(e) => setAdminFirst(e.target.value)} /></Field>
</div>
<Field label="Email (логин) *">
<TextInput type="email" value={adminEmail} onChange={(e) => setAdminEmail(e.target.value)} />
</Field>
<Field label="Должность">
<TextInput value={adminPosition} onChange={(e) => setAdminPosition(e.target.value)} />
</Field>
<p className="text-xs text-slate-500">
Будет сгенерирован временный пароль и показан один раз передайте его сотруднику.
</p>
</section>
<div className="flex gap-3">
<Button variant="secondary" onClick={() => navigate('/super-admin/organizations')}>Отмена</Button>
<Button onClick={submit} disabled={!canSubmit || busy}>
<Save className="w-4 h-4" /> {busy ? 'Создаю…' : 'Создать организацию'}
</Button>
</div>
</div>
<Modal open={!!done} onClose={() => { setDone(null); navigate('/super-admin/organizations') }}
title="Организация создана"
footer={<Button onClick={() => { setDone(null); navigate('/super-admin/organizations') }}>Готово</Button>}>
{done && (
<div className="space-y-3">
<p className="text-sm text-slate-600 dark:text-slate-400">
Передайте администратору данные для входа. Это окно показывается один раз.
</p>
<Field label="Логин (email)">
<div className="flex gap-2">
<TextInput value={done.email} readOnly />
<Button variant="secondary" size="sm" onClick={() => navigator.clipboard?.writeText(done.email)}>
<Copy className="w-4 h-4" />
</Button>
</div>
</Field>
<Field label="Временный пароль">
<div className="flex gap-2">
<TextInput value={done.password} readOnly className="font-mono" />
<Button variant="secondary" size="sm" onClick={() => navigator.clipboard?.writeText(done.password)}>
<Copy className="w-4 h-4" />
</Button>
</div>
</Field>
</div>
)}
</Modal>
</div>
)
}