This commit is contained in:
2026-06-08 10:44:40 +03:00
parent 5adb1e807b
commit 80c7c5e8f8
11 changed files with 157 additions and 9 deletions

View File

@@ -3,12 +3,13 @@ import type {
AdminLoginResponse, AdminLoginResponse,
AdminMeResponse, AdminMeResponse,
CreateOrganizationRequest, CreateOrganizationRequest,
CreateWalletsResponse,
WalletResponse,
DocumentResponse, DocumentResponse,
Organization, Organization,
OrganizationListResponse, OrganizationListResponse,
PurchaseRequestListResponse, PurchaseRequestListResponse,
UpdateOrganizationRequest, UpdateOrganizationRequest,
WalletResponse,
} from '../model/types' } from '../model/types'
const ADMIN_API_URL = 'https://app.admin.elcsa.ru' const ADMIN_API_URL = 'https://app.admin.elcsa.ru'
@@ -113,10 +114,18 @@ export function getOrganization(id: string): Promise<Organization> {
return doAdminRequest<Organization>(`/v1/organizations/${id}`, {}, true) return doAdminRequest<Organization>(`/v1/organizations/${id}`, {}, true)
} }
export function createOrganizationWallets(id: string): Promise<WalletResponse[]> { export function createOrganizationWallets(id: string): Promise<CreateWalletsResponse> {
return doAdminRequest<WalletResponse[]>( return doAdminRequest<CreateWalletsResponse>(
`/v1/organizations/${id}/wallets/create`, `/v1/organizations/${id}/wallets/create`,
{ method: 'POST' }, { method: 'POST', body: JSON.stringify({ id }) },
true,
)
}
export function getOrganizationWallets(id: string): Promise<WalletResponse[]> {
return doAdminRequest<WalletResponse[]>(
`/v1/organizations/${id}/wallets`,
{},
true, true,
) )
} }

View File

@@ -2,15 +2,17 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'
import { createOrganizationWallets } from '../api/adminApi' import { createOrganizationWallets } from '../api/adminApi'
import { ORGANIZATIONS_QUERY_KEY } from './useOrganizations' import { ORGANIZATIONS_QUERY_KEY } from './useOrganizations'
import { ORGANIZATION_QUERY_KEY } from './useOrganization' import { ORGANIZATION_QUERY_KEY } from './useOrganization'
import { WALLETS_QUERY_KEY } from './useOrganizationWallets'
export function useCreateOrganizationWallets() { export function useCreateOrganizationWallets() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
return useMutation({ return useMutation({
mutationFn: (organizationId: string) => createOrganizationWallets(organizationId), mutationFn: (organizationId: string) => createOrganizationWallets(organizationId),
onSuccess: (_wallets, organizationId) => { onSuccess: (_result, organizationId) => {
// `has_wallets` flips to true server-side — refresh list and detail view. // `has_wallets` flips to true server-side — refresh list and detail view.
queryClient.invalidateQueries({ queryKey: ORGANIZATIONS_QUERY_KEY }) queryClient.invalidateQueries({ queryKey: ORGANIZATIONS_QUERY_KEY })
queryClient.invalidateQueries({ queryKey: ORGANIZATION_QUERY_KEY(organizationId) }) queryClient.invalidateQueries({ queryKey: ORGANIZATION_QUERY_KEY(organizationId) })
queryClient.invalidateQueries({ queryKey: WALLETS_QUERY_KEY(organizationId) })
}, },
}) })
} }

View File

@@ -0,0 +1,12 @@
import { useQuery } from '@tanstack/react-query'
import { getOrganizationWallets } from '../api/adminApi'
export const WALLETS_QUERY_KEY = (orgId: string) => ['admin-wallets', orgId]
export function useOrganizationWallets(orgId: string | undefined) {
return useQuery({
queryKey: WALLETS_QUERY_KEY(orgId ?? ''),
queryFn: () => getOrganizationWallets(orgId as string),
enabled: !!orgId,
})
}

View File

@@ -6,6 +6,7 @@ export {
getOrganization, getOrganization,
createOrganization, createOrganization,
createOrganizationWallets, createOrganizationWallets,
getOrganizationWallets,
updateOrganization, updateOrganization,
getDocuments, getDocuments,
uploadDocument, uploadDocument,
@@ -35,6 +36,7 @@ export { useOrganization, ORGANIZATION_QUERY_KEY } from './hooks/useOrganization
export { useCreateOrganization } from './hooks/useCreateOrganization' export { useCreateOrganization } from './hooks/useCreateOrganization'
export { useCreateOrganizationWallets } from './hooks/useCreateOrganizationWallets' export { useCreateOrganizationWallets } from './hooks/useCreateOrganizationWallets'
export { useUpdateOrganization } from './hooks/useUpdateOrganization' export { useUpdateOrganization } from './hooks/useUpdateOrganization'
export { useOrganizationWallets, WALLETS_QUERY_KEY } from './hooks/useOrganizationWallets'
export { useDocuments, DOCUMENTS_QUERY_KEY } from './hooks/useDocuments' export { useDocuments, DOCUMENTS_QUERY_KEY } from './hooks/useDocuments'
export { useUploadDocument } from './hooks/useUploadDocument' export { useUploadDocument } from './hooks/useUploadDocument'
export { usePurchaseRequests, PURCHASE_REQUESTS_QUERY_KEY } from './hooks/usePurchaseRequests' export { usePurchaseRequests, PURCHASE_REQUESTS_QUERY_KEY } from './hooks/usePurchaseRequests'

View File

@@ -58,6 +58,15 @@ export interface WalletResponse {
created_at: string | null created_at: string | null
} }
export interface CreateWalletsRequest {
id: string
}
export interface CreateWalletsResponse {
wallets: WalletResponse[]
mnemonic: string
}
export interface DocumentResponse { export interface DocumentResponse {
id: string id: string
organization_id: string organization_id: string

View File

@@ -6,13 +6,15 @@ import { FormField, Notification, PrimaryButton } from '@shared/ui'
import { AdminLoginForm } from '@widgets/admin-login-form' import { AdminLoginForm } from '@widgets/admin-login-form'
import { OrganizationDocuments } from '@widgets/organization-documents' import { OrganizationDocuments } from '@widgets/organization-documents'
import { OrganizationPurchaseRequests } from '@widgets/organization-purchase-requests' import { OrganizationPurchaseRequests } from '@widgets/organization-purchase-requests'
import { OrganizationWallets } from '@widgets/organization-wallets'
import { useOrganizationForm } from '../model/useOrganizationForm' import { useOrganizationForm } from '../model/useOrganizationForm'
import styles from './AdminOrganizationPage.module.css' import styles from './AdminOrganizationPage.module.css'
type Tab = 'info' | 'documents' | 'requests' type Tab = 'info' | 'wallets' | 'documents' | 'requests'
const TABS: { id: Tab; label: string }[] = [ const TABS: { id: Tab; label: string }[] = [
{ id: 'info', label: 'Общая информация' }, { id: 'info', label: 'Общая информация' },
{ id: 'wallets', label: 'Кошельки' },
{ id: 'documents', label: 'Документы' }, { id: 'documents', label: 'Документы' },
{ id: 'requests', label: 'Заявки' }, { id: 'requests', label: 'Заявки' },
] ]
@@ -118,6 +120,12 @@ export function AdminOrganizationPage() {
</form> </form>
)} )}
{org && activeTab === 'wallets' && (
<div className={styles.tabPanel}>
<OrganizationWallets orgId={org.id} />
</div>
)}
{org && activeTab === 'documents' && ( {org && activeTab === 'documents' && (
<div className={styles.tabPanel}> <div className={styles.tabPanel}>
<OrganizationDocuments orgId={org.id} /> <OrganizationDocuments orgId={org.id} />

View File

@@ -21,10 +21,10 @@ export function AdminPage() {
function handleCreated(organization: Organization) { function handleCreated(organization: Organization) {
setNotification({ status: 'success', message: 'Юридическое лицо добавлено' }) setNotification({ status: 'success', message: 'Юридическое лицо добавлено' })
createWallets.mutate(organization.id, { createWallets.mutate(organization.id, {
onSuccess: (wallets) => { onSuccess: (result) => {
setNotification({ setNotification({
status: 'success', status: 'success',
message: `Кошельки созданы (${wallets.length})`, message: `Кошельки созданы (${result.wallets.length})`,
}) })
}, },
onError: () => { onError: () => {

View File

@@ -0,0 +1 @@
export { OrganizationWallets } from './ui/OrganizationWallets'

View File

@@ -0,0 +1,52 @@
.section {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 20px;
padding: 24px;
}
.sectionTitle {
font-size: 14px;
letter-spacing: 1.5px;
text-transform: uppercase;
color: var(--text-secondary, rgba(255, 255, 255, 0.5));
font-weight: 600;
margin: 0 0 18px;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th {
text-align: left;
font-size: 12px;
letter-spacing: 1.5px;
text-transform: uppercase;
color: var(--text-secondary);
font-weight: 500;
padding: 0 16px 14px;
white-space: nowrap;
}
.table td {
padding: 14px 16px;
border-top: 1px solid rgba(255, 255, 255, 0.06);
vertical-align: middle;
font-size: 14px;
color: var(--text-primary);
}
.mono {
font-family: var(--font-mono, monospace);
font-size: 13px;
word-break: break-all;
}
.state {
padding: 32px 16px;
text-align: center;
color: var(--text-secondary, rgba(255, 255, 255, 0.6));
font-size: 14px;
}

View File

@@ -0,0 +1,53 @@
import { useOrganizationWallets } from '@features/admin'
import styles from './OrganizationWallets.module.css'
interface Props {
orgId: string
}
function formatDate(value: string | null): string {
if (!value) return '—'
const d = new Date(value)
if (Number.isNaN(d.getTime())) return '—'
return d.toLocaleString('ru-RU')
}
export function OrganizationWallets({ orgId }: Props) {
const { data: wallets, isLoading, isError } = useOrganizationWallets(orgId)
return (
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Кошельки</h2>
{isLoading && <div className={styles.state}>Загрузка...</div>}
{isError && <div className={styles.state}>Не удалось загрузить кошельки</div>}
{wallets && wallets.length === 0 && (
<div className={styles.state}>Кошельки ещё не созданы</div>
)}
{wallets && wallets.length > 0 && (
<table className={styles.table}>
<thead>
<tr>
<th>Сеть</th>
<th>Адрес</th>
<th>Derivation path</th>
<th>Создано</th>
</tr>
</thead>
<tbody>
{wallets.map((wallet) => (
<tr key={wallet.id}>
<td>{wallet.chain}</td>
<td className={styles.mono}>{wallet.address}</td>
<td className={styles.mono}>{wallet.derivation_path}</td>
<td>{formatDate(wallet.created_at)}</td>
</tr>
))}
</tbody>
</table>
)}
</section>
)
}

File diff suppressed because one or more lines are too long