feat: integrate ReUI components and update MCP configuration
Build, Test, and Push CFDM Docker Image / test (push) Failing after 49s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped

- Added ReUI configuration to .mcp.json and .cursor/mcp.json for component integration.
- Updated pnpm-lock.yaml with new dependencies including react-phone-number-input and adjustments to existing packages.
- Enhanced SKILL.md documentation for ReUI to clarify usage and features.
- Removed unused components (ChartCard, DataGridCard, etc.) to streamline the codebase.
- Adjusted domain-related components and filters for improved functionality and UI consistency.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-10 00:37:40 +07:00
co-authored by Cursor
parent 59163bfab5
commit 29e57666e0
225 changed files with 21908 additions and 4476 deletions
+78 -91
View File
@@ -3,8 +3,8 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { GlobeIcon } from 'lucide-react'
import { toast } from 'sonner'
import type { Filter } from '@/components/reui/filters'
import { api } from '@/lib/api-client'
import { createDomainSchema, type CreateDomainInput } from '@/lib/schemas'
import {
@@ -13,18 +13,22 @@ import {
groupsQueryOptions,
serviceBindingKeys,
} from '@/queries'
import { PageHeader } from '@/components/page-header'
import { PageShell } from '@/components/page-shell'
import { QueryState } from '@/components/query-state'
import { DomainsDataGrid, type DomainTableRow } from '@/components/domains-data-grid'
import { ResourcePage } from '@/components/reui-kit'
import {
DOMAIN_TABS,
createDefaultDomainFilters,
domainFilterFieldValue,
domainTabFilter,
useDomainColumns,
useDomainFilterFields,
} from '@/components/columns/domains-columns'
import { FormSheet } from '@/components/form-sheet'
import { FormFieldSimple } from '@/components/form-field'
import { LoadingButton } from '@/components/loading-button'
import { TableSkeleton } from '@/components/skeletons'
import { EmptyState } from '@/components/empty-state'
import { AppButton } from '@/components/app-button'
import { AppInput } from '@/components/app-input'
import { AppFieldGroup } from '@/components/app-field'
import { ConfirmDialog } from '@/components/confirm-dialog'
import { Button } from '@cfdm/ui/components/button'
import { Input } from '@cfdm/ui/components/input'
import {
Select,
SelectContent,
@@ -32,6 +36,8 @@ import {
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 } }) =>
@@ -45,18 +51,17 @@ export const Route = createFileRoute('/_auth/domains/')({
function DomainsPage() {
const [sheetOpen, setSheetOpen] = useState(false)
const [importGroupId, setImportGroupId] = useState('')
const [filterGroupId, setFilterGroupId] = useState('')
const [search, setSearch] = useState('')
const listGroupId =
filterGroupId && filterGroupId !== 'none' ? Number(filterGroupId) : undefined
const [deleteTarget, setDeleteTarget] = useState<DomainListItem | null>(null)
const [filters, setFilters] = useState<Filter[]>(createDefaultDomainFilters)
const queryClient = useQueryClient()
const {
data: domains,
isLoading,
isError,
error,
refetch,
} = useQuery(domainsListQueryOptions(listGroupId))
} = useQuery(domainsListQueryOptions())
const { data: groups } = useQuery(groupsQueryOptions())
const groupItems = useMemo(
@@ -67,6 +72,8 @@ function DomainsPage() {
[groups],
)
const filterFields = useDomainFilterFields(groupItems)
const form = useForm<CreateDomainInput>({
resolver: zodResolver(createDomainSchema),
defaultValues: { zone_name: '', group_id: '' },
@@ -91,6 +98,7 @@ function DomainsPage() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: domainKeys.all })
queryClient.invalidateQueries({ queryKey: serviceBindingKeys.all })
setDeleteTarget(null)
toast.success('Зона удалена')
},
onError: (err) => {
@@ -98,9 +106,10 @@ function DomainsPage() {
},
})
function handleDeleteDomain(domain: DomainTableRow) {
deleteMutation.mutate(domain.id)
}
const { columns } = useDomainColumns({
onRequestDelete: setDeleteTarget,
isDeleting: deleteMutation.isPending,
})
const handleCreate = (values: CreateDomainInput) => {
createMutation.mutate({
@@ -115,86 +124,47 @@ function DomainsPage() {
form.setValue('group_id', next)
}
const handleFilterGroupChange = (value: string | null) => {
if (!value || value === 'all') {
setFilterGroupId('')
return
}
setFilterGroupId(value === 'none' ? 'none' : value)
}
const filteredDomains = useMemo(() => {
if (filterGroupId === 'none') {
return domains?.filter((d) => d.group_id === null) ?? []
}
return domains ?? []
}, [domains, filterGroupId])
const tableData = useMemo(
() => filteredDomains as DomainTableRow[],
[filteredDomains],
)
const hasActiveFilter = filterGroupId !== ''
const isZeroResults = hasActiveFilter && tableData.length === 0 && (domains?.length ?? 0) > 0
const isTrulyEmpty = tableData.length === 0 && !hasActiveFilter
const emptyAction = isZeroResults ? (
<AppButton variant="outline" onClick={() => setFilterGroupId('')}>
Сбросить фильтр
</AppButton>
) : (
<AppButton onClick={() => setSheetOpen(true)}>Импортировать домен</AppButton>
const primaryAction = (
<>
<Button type="button" variant="outline" nativeButton={false} render={<Link to="/groups" />}>
Канбан групп
</Button>
<Button type="button" onClick={() => setSheetOpen(true)}>
Импортировать домен
</Button>
</>
)
return (
<PageShell>
<PageHeader
<ResourcePage
title="Домены"
description={`${tableData.length} зон · ${tableData.reduce((sum, d) => sum + d.service_count, 0)} привязок сервисов`}
actions={
<>
<AppButton variant="outline" nativeButton={false} render={<Link to="/groups" />}>
Канбан групп
</AppButton>
<AppButton onClick={() => setSheetOpen(true)}>Импортировать домен</AppButton>
</>
}
/>
<QueryState
description="Импортированные зоны Cloudflare"
tabs={DOMAIN_TABS.map((tab) => ({ ...tab }))}
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}
skeleton={<TableSkeleton />}
>
{isTrulyEmpty || isZeroResults ? (
<EmptyState
icon={GlobeIcon}
title={isZeroResults ? 'Ничего не найдено' : 'Домены не импортированы'}
description={
isZeroResults
? 'По выбранному фильтру группы нет подходящих зон'
: 'Импортируйте зону из аккаунта Cloudflare'
}
action={emptyAction}
/>
) : (
<DomainsDataGrid
data={tableData}
totalCount={tableData.length}
groupFilterItems={groupItems}
groupFilterValue={filterGroupId || 'all'}
onGroupFilterChange={handleFilterGroupChange}
search={search}
onSearchChange={setSearch}
onDelete={handleDeleteDomain}
isDeleting={deleteMutation.isPending}
emptyAction={emptyAction}
/>
)}
</QueryState>
primaryAction={primaryAction}
emptyState={{
title: 'Домены не импортированы',
description: 'Импортируйте зону из аккаунта Cloudflare',
action: (
<Button type="button" onClick={() => setSheetOpen(true)}>
Импортировать домен
</Button>
),
}}
/>
<FormSheet
open={sheetOpen}
@@ -214,13 +184,13 @@ function DomainsPage() {
</LoadingButton>
}
>
<AppFieldGroup>
<FieldGroup>
<FormFieldSimple
label="Имя зоны"
htmlFor="zone_name"
error={form.formState.errors.zone_name}
>
<AppInput
<Input
id="zone_name"
placeholder="example.com"
{...form.register('zone_name')}
@@ -245,8 +215,25 @@ function DomainsPage() {
</SelectContent>
</Select>
</FormFieldSimple>
</AppFieldGroup>
</FieldGroup>
</FormSheet>
<ConfirmDialog
open={deleteTarget !== null}
onOpenChange={(open) => {
if (!open) setDeleteTarget(null)
}}
title="Удалить зону?"
description={
deleteTarget
? `Зона «${deleteTarget.zone_name}» будет удалена из менеджера вместе с DNS-записями и привязками. Зона в Cloudflare не затрагивается.`
: ''
}
onConfirm={() => {
if (deleteTarget) deleteMutation.mutate(deleteTarget.id)
}}
disabled={deleteMutation.isPending}
/>
</PageShell>
)
}