refactor(ui): выровнять lists и domain detail под ResourcePage
Bulk toolbar на Frame/muted; availability на Field+Item; domain detail на CountedLineTabs; group detail с DetailPanel. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -18,20 +18,6 @@ import { LoadingButton } from '@/components/loading-button'
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { QueryState } from '@/components/query-state'
|
||||
import { TableSkeleton } from '@/components/skeletons'
|
||||
import {
|
||||
AppField,
|
||||
AppFieldGroup,
|
||||
AppFieldLabel,
|
||||
} from '@/components/app-field'
|
||||
import { AppInput } from '@/components/app-input'
|
||||
import {
|
||||
AppItem,
|
||||
AppItemActions,
|
||||
AppItemContent,
|
||||
AppItemGroup,
|
||||
AppItemSeparator,
|
||||
AppItemTitle,
|
||||
} from '@/components/app-item'
|
||||
import {
|
||||
Frame,
|
||||
FrameDescription,
|
||||
@@ -40,6 +26,16 @@ import {
|
||||
FrameTitle,
|
||||
} from '@/components/reui/frame'
|
||||
import { Button } from '@cfdm/ui/components/button'
|
||||
import { Field, FieldGroup, FieldLabel } from '@cfdm/ui/components/field'
|
||||
import { Input } from '@cfdm/ui/components/input'
|
||||
import {
|
||||
Item,
|
||||
ItemActions,
|
||||
ItemContent,
|
||||
ItemGroup,
|
||||
ItemSeparator,
|
||||
ItemTitle,
|
||||
} from '@cfdm/ui/components/item'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -178,21 +174,19 @@ export function DomainAvailabilityPanel({
|
||||
createMutation.mutate()
|
||||
}}
|
||||
>
|
||||
<AppFieldGroup className="grid gap-4 sm:grid-cols-[1fr_10rem_auto] sm:items-end">
|
||||
<AppField>
|
||||
<AppFieldLabel htmlFor="monitor-hostname">
|
||||
Hostname
|
||||
</AppFieldLabel>
|
||||
<AppInput
|
||||
<FieldGroup className="grid gap-4 sm:grid-cols-[1fr_10rem_auto] sm:items-end">
|
||||
<Field>
|
||||
<FieldLabel htmlFor="monitor-hostname">Hostname</FieldLabel>
|
||||
<Input
|
||||
id="monitor-hostname"
|
||||
value={hostname}
|
||||
placeholder="example.com"
|
||||
onChange={(e) => setHostname(e.target.value)}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</AppField>
|
||||
<AppField>
|
||||
<AppFieldLabel htmlFor="monitor-type">Тип</AppFieldLabel>
|
||||
</Field>
|
||||
<Field>
|
||||
<FieldLabel htmlFor="monitor-type">Тип</FieldLabel>
|
||||
<Select
|
||||
items={MONITOR_TYPE_ITEMS}
|
||||
value={monitorType}
|
||||
@@ -213,7 +207,7 @@ export function DomainAvailabilityPanel({
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</AppField>
|
||||
</Field>
|
||||
<LoadingButton
|
||||
type="submit"
|
||||
isLoading={createMutation.isPending}
|
||||
@@ -223,7 +217,7 @@ export function DomainAvailabilityPanel({
|
||||
<PlusIcon data-icon="inline-start" />
|
||||
Добавить
|
||||
</LoadingButton>
|
||||
</AppFieldGroup>
|
||||
</FieldGroup>
|
||||
</form>
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
@@ -251,14 +245,14 @@ export function DomainAvailabilityPanel({
|
||||
description="Добавьте hostname для проверки доступности"
|
||||
/>
|
||||
) : (
|
||||
<AppItemGroup className="gap-0">
|
||||
<ItemGroup className="gap-0">
|
||||
{monitors.map((monitor, index) => (
|
||||
<div key={monitor.id}>
|
||||
<AppItem size="sm" variant="muted" className="border-0 px-0">
|
||||
<AppItemContent className="gap-1">
|
||||
<AppItemTitle className="font-mono">
|
||||
<Item size="sm" variant="muted" className="border-0 px-0">
|
||||
<ItemContent className="gap-1">
|
||||
<ItemTitle className="font-mono">
|
||||
{monitor.hostname}
|
||||
</AppItemTitle>
|
||||
</ItemTitle>
|
||||
<div className="text-muted-foreground flex flex-wrap items-center gap-2 text-sm">
|
||||
<span className="uppercase">
|
||||
{monitorTypeLabel(monitor.type)}
|
||||
@@ -271,8 +265,8 @@ export function DomainAvailabilityPanel({
|
||||
<span>Ещё не проверялся</span>
|
||||
)}
|
||||
</div>
|
||||
</AppItemContent>
|
||||
<AppItemActions className="gap-2">
|
||||
</ItemContent>
|
||||
<ItemActions className="gap-2">
|
||||
<HealthCheckBadge
|
||||
status={monitor.last_status}
|
||||
latencyMs={monitor.last_latency_ms}
|
||||
@@ -299,14 +293,12 @@ export function DomainAvailabilityPanel({
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</AppItemActions>
|
||||
</AppItem>
|
||||
{index < monitors.length - 1 ? (
|
||||
<AppItemSeparator />
|
||||
) : null}
|
||||
</ItemActions>
|
||||
</Item>
|
||||
{index < monitors.length - 1 ? <ItemSeparator /> : null}
|
||||
</div>
|
||||
))}
|
||||
</AppItemGroup>
|
||||
</ItemGroup>
|
||||
)}
|
||||
</QueryState>
|
||||
</FramePanel>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { Badge } from '@/components/reui/badge'
|
||||
import { Frame, FramePanel } from '@/components/reui/frame'
|
||||
import { Button } from '@cfdm/ui/components/button'
|
||||
import {
|
||||
Select,
|
||||
@@ -42,89 +44,97 @@ export function DomainsBulkToolbar({
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="mb-3 flex flex-wrap items-center gap-2 rounded-lg border bg-muted/40 px-3 py-2">
|
||||
<span className="text-muted-foreground text-sm tabular-nums">
|
||||
Выбрано: {count}
|
||||
</span>
|
||||
<Select
|
||||
items={groupItems}
|
||||
value={groupValue}
|
||||
onValueChange={(v) => setGroupValue(v ?? 'none')}
|
||||
>
|
||||
<SelectTrigger className="w-44" size="sm">
|
||||
<SelectValue placeholder="Группа" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{groupItems.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
onAssignGroup(groupValue === 'none' ? null : Number(groupValue))
|
||||
}
|
||||
>
|
||||
В группу
|
||||
</Button>
|
||||
<Select
|
||||
items={envItems}
|
||||
value={envValue}
|
||||
onValueChange={(v) => setEnvValue(v ?? 'none')}
|
||||
>
|
||||
<SelectTrigger className="w-32" size="sm">
|
||||
<SelectValue placeholder="Env" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{envItems.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
onSetEnvironment(
|
||||
envValue === 'none'
|
||||
? null
|
||||
: (envValue as 'prod' | 'staging' | 'dev'),
|
||||
)
|
||||
}
|
||||
>
|
||||
Env
|
||||
</Button>
|
||||
<Input
|
||||
className="h-8 w-32"
|
||||
placeholder="тег"
|
||||
value={tag}
|
||||
onChange={(e) => setTag(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isPending || !tag.trim()}
|
||||
onClick={() => {
|
||||
onAddTag(tag.trim())
|
||||
setTag('')
|
||||
}}
|
||||
>
|
||||
+ тег
|
||||
</Button>
|
||||
<Button type="button" variant="ghost" size="sm" disabled={isPending} onClick={onClear}>
|
||||
Снять выделение
|
||||
</Button>
|
||||
</div>
|
||||
<Frame dense spacing="sm" className="mb-3 w-full">
|
||||
<FramePanel className="flex flex-wrap items-center gap-2 bg-muted/30">
|
||||
<Badge size="sm" variant="secondary" className="tabular-nums">
|
||||
{count} выбрано
|
||||
</Badge>
|
||||
<Select
|
||||
items={groupItems}
|
||||
value={groupValue}
|
||||
onValueChange={(v) => setGroupValue(v ?? 'none')}
|
||||
>
|
||||
<SelectTrigger className="w-44" size="sm">
|
||||
<SelectValue placeholder="Группа" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{groupItems.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
onAssignGroup(groupValue === 'none' ? null : Number(groupValue))
|
||||
}
|
||||
>
|
||||
В группу
|
||||
</Button>
|
||||
<Select
|
||||
items={envItems}
|
||||
value={envValue}
|
||||
onValueChange={(v) => setEnvValue(v ?? 'none')}
|
||||
>
|
||||
<SelectTrigger className="w-32" size="sm">
|
||||
<SelectValue placeholder="Env" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{envItems.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isPending}
|
||||
onClick={() =>
|
||||
onSetEnvironment(
|
||||
envValue === 'none'
|
||||
? null
|
||||
: (envValue as 'prod' | 'staging' | 'dev'),
|
||||
)
|
||||
}
|
||||
>
|
||||
Env
|
||||
</Button>
|
||||
<Input
|
||||
className="h-8 w-32"
|
||||
placeholder="тег"
|
||||
value={tag}
|
||||
onChange={(e) => setTag(e.target.value)}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled={isPending || !tag.trim()}
|
||||
onClick={() => {
|
||||
onAddTag(tag.trim())
|
||||
setTag('')
|
||||
}}
|
||||
>
|
||||
+ тег
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
disabled={isPending}
|
||||
onClick={onClear}
|
||||
>
|
||||
Снять выделение
|
||||
</Button>
|
||||
</FramePanel>
|
||||
</Frame>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user