import { createFileRoute } from '@tanstack/react-router' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { useCallback, useMemo, useState } from 'react' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { toast } from 'sonner' import type { Filter } from '@/components/reui/filters' import { api } from '@/lib/api-client' import { createDomainSchema, type BulkUpdateDomainsInput, type CreateDomainInput } from '@/lib/schemas' import { bulkUpdateDomains, domainKeys, domainsListQueryOptions, groupsQueryOptions, serviceBindingKeys, } from '@/queries' import { PageShell } from '@/components/page-shell' import { PageHeader } from '@/components/page-header' import { DomainKpiCards } from '@/components/domain-kpi-cards' import { ResourcePage } from '@/components/reui-kit' import { DOMAIN_TABS, createDefaultDomainFilters, domainFilterFieldValue, domainTabFilter, useDomainColumns, useDomainFilterFields, } from '@/components/columns/domains-columns' import { DomainsBulkToolbar } from '@/components/domains/domains-bulk-toolbar' import { FormSheet } from '@/components/form-sheet' import { FormFieldSimple } from '@/components/form-field' import { LoadingButton } from '@/components/loading-button' import { ConfirmDialog } from '@/components/confirm-dialog' import { Button } from '@cfdm/ui/components/button' import { Input } from '@cfdm/ui/components/input' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@cfdm/ui/components/select' import { FieldGroup } from '@cfdm/ui/components/field' import type { DomainListItem } from '@/lib/schemas' export const Route = createFileRoute('/_auth/domains/')({ loader: ({ context: { queryClient } }) => Promise.all([ queryClient.ensureQueryData(domainsListQueryOptions()), queryClient.ensureQueryData(groupsQueryOptions()), ]), component: DomainsPage, }) function DomainsPage() { const [sheetOpen, setSheetOpen] = useState(false) const [importGroupId, setImportGroupId] = useState('') const [deleteTarget, setDeleteTarget] = useState(null) const [filters, setFilters] = useState(createDefaultDomainFilters) const [activeTab, setActiveTab] = useState('all') const queryClient = useQueryClient() const handleSelectTab = useCallback((tabId: string) => setActiveTab(tabId), []) const { data: domains, isLoading, isError, error, refetch, } = useQuery(domainsListQueryOptions()) const { data: groups } = useQuery(groupsQueryOptions()) const groupItems = useMemo( () => [ { label: 'Без группы', value: 'none' }, ...(groups?.map((g) => ({ label: g.name, value: String(g.id) })) ?? []), ], [groups], ) const filterFields = useDomainFilterFields(groupItems) const form = useForm({ resolver: zodResolver(createDomainSchema), defaultValues: { zone_name: '', group_id: '' }, }) const createMutation = useMutation({ mutationFn: (body: { zone_name: string; group_id?: number }) => api.post('/api/v1/domains', body), onSuccess: () => { queryClient.invalidateQueries({ queryKey: domainKeys.all }) form.reset({ zone_name: '', group_id: importGroupId }) setSheetOpen(false) toast.success('Домен импортирован') }, onError: (err) => { toast.error(err instanceof Error ? err.message : 'Не удалось импортировать домен') }, }) const deleteMutation = useMutation({ mutationFn: (id: number) => api.delete(`/api/v1/domains/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: domainKeys.all }) queryClient.invalidateQueries({ queryKey: serviceBindingKeys.all }) setDeleteTarget(null) toast.success('Зона удалена') }, onError: (err) => { toast.error(err instanceof Error ? err.message : 'Не удалось удалить зону') }, }) const bulkMutation = useMutation({ mutationFn: (body: BulkUpdateDomainsInput) => bulkUpdateDomains(body), onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: domainKeys.all }) toast.success(`Обновлено: ${data.updated}`) }, onError: (err) => { toast.error(err instanceof Error ? err.message : 'Не удалось обновить') }, }) const { columns } = useDomainColumns({ onRequestDelete: setDeleteTarget, isDeleting: deleteMutation.isPending, enableSelection: true, }) const handleCreate = (values: CreateDomainInput) => { createMutation.mutate({ zone_name: values.zone_name.trim(), group_id: importGroupId ? Number(importGroupId) : undefined, }) } const handleImportGroupChange = (value: string | null) => { const next = value === 'none' || !value ? '' : value setImportGroupId(next) form.setValue('group_id', next) } const primaryAction = ( ) return ( ({ ...tab }))} activeTab={activeTab} onTabChange={setActiveTab} tabFilter={domainTabFilter} filterFields={filterFields} filters={filters} onFiltersChange={setFilters} onClearFilters={() => setFilters(createDefaultDomainFilters())} getFilterFieldValue={domainFilterFieldValue} columns={columns} data={domains ?? []} getRowId={(row) => String(row.id)} isLoading={isLoading} isError={isError} error={error} onRetry={refetch} enableRowSelection selectionToolbar={({ selectedIds, clearSelection }) => ( { bulkMutation.mutate( { ids: selectedIds.map(Number), group_id: groupId }, { onSuccess: () => clearSelection() }, ) }} onSetEnvironment={(environment) => { bulkMutation.mutate( { ids: selectedIds.map(Number), environment }, { onSuccess: () => clearSelection() }, ) }} onAddTag={(tag) => { bulkMutation.mutate( { ids: selectedIds.map(Number), tags_add: [tag] }, { onSuccess: () => clearSelection() }, ) }} onClear={clearSelection} /> )} emptyState={{ title: 'Домены не импортированы', description: 'Импортируйте зону из аккаунта Cloudflare', action: ( ), }} /> Импортировать } > { if (!open) setDeleteTarget(null) }} title="Удалить зону?" description={ deleteTarget ? `Зона «${deleteTarget.zone_name}» будет удалена из менеджера вместе с DNS-записями и привязками. Зона в Cloudflare не затрагивается.` : '' } onConfirm={() => { if (deleteTarget) deleteMutation.mutate(deleteTarget.id) }} disabled={deleteMutation.isPending} /> ) }