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
194 lines
6.6 KiB
TypeScript
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>
|
|
)
|
|
}
|