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
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:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user