admin page

This commit is contained in:
2026-06-05 22:33:02 +03:00
parent fd66ca9c9b
commit f4af2fd137
17 changed files with 508 additions and 43 deletions

View File

@@ -12,7 +12,6 @@ interface FormState {
contact_person: string
contact_phone: string
status: string
bank_details: string
}
function toForm(org: Organization): FormState {
@@ -26,7 +25,6 @@ function toForm(org: Organization): FormState {
contact_person: org.contact_person ?? '',
contact_phone: org.contact_phone ?? '',
status: org.status ?? '',
bank_details: org.bank_details ? JSON.stringify(org.bank_details, null, 2) : '',
}
}
@@ -47,10 +45,9 @@ export function useOrganizationForm(
const [form, setForm] = useState<FormState>(() =>
org ? toForm(org) : {
name: '', short_name: '', ogrn: '', kpp: '', legal_address: '',
actual_address: '', contact_person: '', contact_phone: '', status: '', bank_details: '',
actual_address: '', contact_person: '', contact_phone: '', status: '',
},
)
const [bankError, setBankError] = useState<string | null>(null)
const mutation = useUpdateOrganization(id)
// Sync local form state once the organization loads / changes.
@@ -63,20 +60,9 @@ export function useOrganizationForm(
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
setBankError(null)
const trimmedOrNull = (v: string) => (v.trim() ? v.trim() : null)
let bank_details: Record<string, unknown> | null = null
if (form.bank_details.trim()) {
try {
bank_details = JSON.parse(form.bank_details)
} catch {
setBankError('Банковские реквизиты должны быть корректным JSON')
return
}
}
const payload: UpdateOrganizationRequest = {
name: form.name.trim(),
short_name: trimmedOrNull(form.short_name),
@@ -87,14 +73,12 @@ export function useOrganizationForm(
contact_person: trimmedOrNull(form.contact_person),
contact_phone: trimmedOrNull(form.contact_phone),
status: trimmedOrNull(form.status),
bank_details,
}
mutation.mutate(payload, { onSuccess: () => onSaved?.() })
}
const error =
bankError ?? (mutation.isError ? extractErrorMessage(mutation.error) : null)
const error = mutation.isError ? extractErrorMessage(mutation.error) : null
return {
form,

View File

@@ -39,6 +39,11 @@
gap: 24px;
}
.documents {
max-width: 900px;
margin: 24px auto 0;
}
.section {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);

View File

@@ -4,6 +4,7 @@ import { useAdminAuth, useOrganization } from '@features/admin'
import { ROUTES } from '@shared/config/routes'
import { FormField, Notification, PrimaryButton } from '@shared/ui'
import { AdminLoginForm } from '@widgets/admin-login-form'
import { OrganizationDocuments } from '@widgets/organization-documents'
import { useOrganizationForm } from '../model/useOrganizationForm'
import styles from './AdminOrganizationPage.module.css'
@@ -71,19 +72,6 @@ export function AdminOrganizationPage() {
</div>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Банковские реквизиты</h2>
<label className={styles.bankLabel}>JSON-объект реквизитов</label>
<textarea
className={styles.textarea}
value={form.bank_details}
onChange={(e) => setField('bank_details')(e.target.value)}
placeholder={'{\n "bank_name": "...",\n "bik": "...",\n "account": "..."\n}'}
rows={6}
spellCheck={false}
/>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Системная информация</h2>
<div className={styles.grid}>
@@ -105,6 +93,12 @@ export function AdminOrganizationPage() {
</form>
)}
{org && (
<div className={styles.documents}>
<OrganizationDocuments orgId={org.id} />
</div>
)}
{notice && (
<Notification
status="success"

View File

@@ -1,16 +1,40 @@
import { useState } from 'react'
import { useAdminAuth, useAdminLogout } from '@features/admin'
import { useAdminAuth, useAdminLogout, useCreateOrganizationWallets } from '@features/admin'
import type { Organization } from '@features/admin'
import { Notification } from '@shared/ui'
import { AdminLoginForm } from '@widgets/admin-login-form'
import { LegalEntitiesTable } from '@widgets/legal-entities-table'
import { AddLegalEntityModal } from '@widgets/add-legal-entity-modal'
import styles from './AdminPage.module.css'
type NotificationState = { message: string; status: 'success' | 'error' | 'warning' }
export function AdminPage() {
const { isAuthenticated, isLoading } = useAdminAuth()
const logout = useAdminLogout()
const createWallets = useCreateOrganizationWallets()
const [modalOpen, setModalOpen] = useState(false)
const [notification, setNotification] = useState<{ message: string; status: 'success' | 'error' } | null>(null)
const [notification, setNotification] = useState<NotificationState | null>(null)
// After a legal entity is created we immediately provision its wallets.
// The page stays mounted (unlike the modal), so these mutate callbacks fire reliably.
function handleCreated(organization: Organization) {
setNotification({ status: 'success', message: 'Юридическое лицо добавлено' })
createWallets.mutate(organization.id, {
onSuccess: (wallets) => {
setNotification({
status: 'success',
message: `Кошельки созданы (${wallets.length})`,
})
},
onError: () => {
setNotification({
status: 'warning',
message: 'Юридическое лицо создано, но кошельки создать не удалось',
})
},
})
}
if (isLoading) return null
if (!isAuthenticated) return <AdminLoginForm />
@@ -38,7 +62,7 @@ export function AdminPage() {
<AddLegalEntityModal
open={modalOpen}
onClose={() => setModalOpen(false)}
onCreated={() => setNotification({ status: 'success', message: 'Юридическое лицо добавлено' })}
onCreated={handleCreated}
/>
{notification && (