Files
cloudflare-domain-manager/apps/web/src/components/services-board/service-group-item.tsx
T
Denozordec 2147782ba6
Build, Test, and Push CFDM Docker Image / test (push) Successful in 3m55s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Successful in 2m14s
Build, Test, and Push CFDM Docker Image / update-wiki (push) Successful in 7s
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
refactor: Replace legacy UI components with new App components across various files for improved consistency and maintainability
2026-06-25 16:54:21 +07:00

194 lines
6.6 KiB
TypeScript

import { useEffect } from 'react'
import { useDroppable } from '@dnd-kit/core'
import {
SortableContext,
verticalListSortingStrategy,
} from '@dnd-kit/sortable'
import { PlusIcon } from 'lucide-react'
import { ServiceGroupHeader } from '@/components/services-board/service-group-header'
import { ServiceRow } from '@/components/services-board/service-row'
import type { BoardColumn } from '@/components/services-board/types'
import type { ServiceGroupView, ServiceView } from '@/lib/schemas'
import {
AppAccordionContent,
AppAccordionItem,
} from '@/components/app-accordion'
import { AppButton } from '@/components/app-button'
import {
Empty,
EmptyContent,
EmptyDescription,
EmptyHeader,
EmptyTitle,
} from '@cfdm/ui/components/empty'
import { AppItemGroup, AppItemSeparator } from '@/components/app-item'
import { cn } from '@cfdm/ui/lib/utils'
interface ServiceGroupItemProps {
column: BoardColumn
isOpen?: boolean
isDragging?: boolean
onExpandColumn?: (columnId: string) => void
onGroupToggle?: (groupId: number, enabled: boolean) => void
onEditGroup?: (group: ServiceGroupView) => void
onDeleteGroup?: (group: ServiceGroupView) => void
onAddService?: (groupId: number | null) => void
onServiceToggle: (serviceId: number, enabled: boolean) => void
onEditService: (service: ServiceView) => void
onDeleteService: (service: ServiceView) => void
togglingGroupId?: number | null
togglingServiceId?: number | null
dragDisabled?: boolean
showCheckbox?: boolean
isSelected?: (id: number) => boolean
onSelectedChange?: (id: number, selected: boolean) => void
isAllSelected?: (ids: number[]) => boolean
isSomeSelected?: (ids: number[]) => boolean
onSelectAllInGroup?: (ids: number[]) => void
onDeselectAllInGroup?: (ids: number[]) => void
}
export function ServiceGroupItem({
column,
isOpen = true,
isDragging = false,
onExpandColumn,
onGroupToggle,
onEditGroup,
onDeleteGroup,
onAddService,
onServiceToggle,
onEditService,
onDeleteService,
togglingGroupId = null,
togglingServiceId = null,
dragDisabled = false,
showCheckbox = false,
isSelected,
onSelectedChange,
isAllSelected,
isSomeSelected,
onSelectAllInGroup,
onDeselectAllInGroup,
}: ServiceGroupItemProps) {
const { setNodeRef, isOver } = useDroppable({
id: column.id,
disabled: dragDisabled,
})
const isGroupToggling = column.groupId !== null && togglingGroupId === column.groupId
const serviceDisabled = Boolean(column.domain) && !column.enabled
const serviceIds = column.items.map((s) => s.id)
const groupAllSelected = isAllSelected?.(serviceIds) ?? false
const groupSomeSelected = isSomeSelected?.(serviceIds) ?? false
useEffect(() => {
if (isOver && isDragging && !dragDisabled) {
onExpandColumn?.(column.id)
}
}, [isOver, isDragging, dragDisabled, column.id, onExpandColumn])
return (
<AppAccordionItem
value={column.id}
className={cn(
'not-last:border-b-0 overflow-hidden rounded-lg border border-border transition-colors',
!isOpen && 'bg-muted/20',
isOpen && 'bg-muted/30',
isOver && !dragDisabled && 'ring-2 ring-primary/30',
)}
>
<ServiceGroupHeader
column={column}
isOpen={isOpen}
isDragging={isDragging}
dragDisabled={dragDisabled}
showCheckbox={showCheckbox}
groupAllSelected={groupAllSelected}
groupSomeSelected={groupSomeSelected}
serviceIds={serviceIds}
isGroupToggling={isGroupToggling}
onSelectAllInGroup={onSelectAllInGroup}
onDeselectAllInGroup={onDeselectAllInGroup}
onGroupToggle={onGroupToggle}
onEditGroup={onEditGroup}
onDeleteGroup={onDeleteGroup}
/>
<AppAccordionContent className="px-1 pb-2">
<div ref={setNodeRef}>
<SortableContext
items={column.items.map((service) => String(service.id))}
strategy={verticalListSortingStrategy}
>
{column.items.length > 0 ? (
<AppItemGroup className="gap-0 py-1">
{column.items.map((service, index) => (
<div key={service.id}>
{index > 0 ? <AppItemSeparator className="my-0" /> : null}
<ServiceRow
service={service}
onToggle={onServiceToggle}
onEdit={onEditService}
onDelete={onDeleteService}
isToggling={togglingServiceId === service.id}
disabled={serviceDisabled}
dragDisabled={dragDisabled}
showCheckbox={showCheckbox}
selected={isSelected?.(service.id) ?? false}
onSelectedChange={(selected) =>
onSelectedChange?.(service.id, selected)
}
/>
</div>
))}
</AppItemGroup>
) : (
<Empty
className={cn(
'border border-dashed py-2',
isOver && !dragDisabled && 'border-primary bg-primary/5',
)}
>
<EmptyHeader>
<EmptyTitle className="text-sm">Нет сервисов в группе</EmptyTitle>
<EmptyDescription>
{dragDisabled
? 'Сервисы не найдены'
: 'Перетащите сервис сюда или добавьте новый'}
</EmptyDescription>
</EmptyHeader>
{onAddService ? (
<EmptyContent>
<AppButton
type="button"
variant="link"
size="sm"
onClick={() => onAddService(column.groupId)}
>
<PlusIcon data-icon="inline-start" />
Добавить сервис
</AppButton>
</EmptyContent>
) : null}
</Empty>
)}
</SortableContext>
{column.items.length > 0 && onAddService ? (
<AppButton
type="button"
variant="ghost"
size="sm"
className="mt-1 w-full justify-start text-muted-foreground"
onClick={() => onAddService(column.groupId)}
>
<PlusIcon data-icon="inline-start" />
Добавить сервис
</AppButton>
) : null}
</div>
</AppAccordionContent>
</AppAccordionItem>
)
}