feat(bases): hide and show kanban columns from header menu

This commit is contained in:
Philipinho
2026-05-24 15:53:49 +01:00
parent beb7120b00
commit a7d390932d
3 changed files with 107 additions and 14 deletions
@@ -1,4 +1,6 @@
import { useCallback, useMemo } from "react";
import { Badge } from "@mantine/core";
import { IconPlus } from "@tabler/icons-react";
import {
IBase,
IBaseRow,
@@ -168,23 +170,91 @@ export function BaseKanban({
[base.id, columns, effectiveView, updateViewMutation],
);
const handleHideColumn = useCallback(
(columnKey: string) => {
if (!effectiveView) return;
const current = effectiveView.config?.hiddenChoiceIds ?? [];
if (current.includes(columnKey)) return;
updateViewMutation.mutate({
viewId: effectiveView.id,
pageId: base.id,
config: {
...effectiveView.config,
hiddenChoiceIds: [...current, columnKey],
},
});
},
[base.id, effectiveView, updateViewMutation],
);
const handleShowColumn = useCallback(
(columnKey: string) => {
if (!effectiveView) return;
const next = (effectiveView.config?.hiddenChoiceIds ?? []).filter(
(id) => id !== columnKey,
);
updateViewMutation.mutate({
viewId: effectiveView.id,
pageId: base.id,
config: { ...effectiveView.config, hiddenChoiceIds: next },
});
},
[base.id, effectiveView, updateViewMutation],
);
const hiddenIds = effectiveView?.config?.hiddenChoiceIds ?? [];
const hiddenChoices = useMemo(() => {
if (!isGroupable || hiddenIds.length === 0) return [];
const opts = (property!.typeOptions as { choices?: Array<{ id: string; name: string; color: string }> } | undefined) ?? {};
const choices = opts.choices ?? [];
const byId = new Map(choices.map((c) => [c.id, c]));
return hiddenIds
.map((id) =>
id === NO_VALUE_CHOICE_ID
? { id, name: "No value", color: null as string | null }
: byId.get(id)
? { id, name: byId.get(id)!.name, color: byId.get(id)!.color as string | null }
: null,
)
.filter((c): c is { id: string; name: string; color: string | null } => c !== null);
}, [hiddenIds, isGroupable, property]);
if (!isGroupable) {
return <KanbanEmptyState base={base} onPick={handlePickProperty} />;
}
return (
<div className={classes.board}>
{columns.map((column) => (
<KanbanColumn
key={column.key}
column={column}
primaryProperty={primaryProperty}
onCardClick={onCardClick}
onAddCard={handleAddCard}
onCardDrop={handleCardDrop}
onColumnReorder={handleColumnReorder}
/>
))}
</div>
<>
{hiddenChoices.length > 0 && (
<div style={{ padding: "6px 12px", display: "flex", gap: 6, flexWrap: "wrap" }}>
{hiddenChoices.map((c) => (
<Badge
key={c.id}
color={c.color ?? "gray"}
variant="outline"
style={{ cursor: "pointer" }}
onClick={() => handleShowColumn(c.id)}
rightSection={<IconPlus size={12} />}
>
{c.name}
</Badge>
))}
</div>
)}
<div className={classes.board}>
{columns.map((column) => (
<KanbanColumn
key={column.key}
column={column}
primaryProperty={primaryProperty}
onCardClick={onCardClick}
onAddCard={handleAddCard}
onCardDrop={handleCardDrop}
onColumnReorder={handleColumnReorder}
onHide={handleHideColumn}
/>
))}
</div>
</>
);
}
@@ -1,4 +1,6 @@
import { Badge, Text } from "@mantine/core";
import { ActionIcon, Badge, Menu, Text } from "@mantine/core";
import { IconDots, IconEyeOff } from "@tabler/icons-react";
import { useTranslation } from "react-i18next";
import {
useKanbanColumnReorder,
type ColumnReorderPayload,
@@ -12,6 +14,7 @@ type KanbanColumnHeaderProps = {
color: string | null;
count: number;
onReorderDrop: (payload: ColumnReorderPayload) => void;
onHide: (columnKey: string) => void;
};
export function KanbanColumnHeader({
@@ -20,7 +23,9 @@ export function KanbanColumnHeader({
color,
count,
onReorderDrop,
onHide,
}: KanbanColumnHeaderProps) {
const { t } = useTranslation();
const { ref, isDragging, closestEdge } = useKanbanColumnReorder({
columnKey,
onDrop: onReorderDrop,
@@ -43,6 +48,21 @@ export function KanbanColumnHeader({
)}
<span className={classes.columnCount}>{count}</span>
</div>
<Menu shadow="md" width={160} position="bottom-end">
<Menu.Target>
<ActionIcon variant="subtle" size="sm" color="gray">
<IconDots size={14} />
</ActionIcon>
</Menu.Target>
<Menu.Dropdown>
<Menu.Item
leftSection={<IconEyeOff size={14} />}
onClick={() => onHide(columnKey)}
>
{t("Hide group")}
</Menu.Item>
</Menu.Dropdown>
</Menu>
{closestEdge && <BaseDropEdgeIndicator edge={closestEdge} />}
</div>
);
@@ -15,6 +15,7 @@ type KanbanColumnProps = {
onAddCard: (columnKey: string) => void;
onCardDrop: (payload: CardDropPayload) => void;
onColumnReorder: (payload: ColumnReorderPayload) => void;
onHide: (columnKey: string) => void;
};
export function KanbanColumn({
@@ -24,6 +25,7 @@ export function KanbanColumn({
onAddCard,
onCardDrop,
onColumnReorder,
onHide,
}: KanbanColumnProps) {
const { ref: bodyRef, isOver } = useKanbanColumnDrop({
columnKey: column.key,
@@ -38,6 +40,7 @@ export function KanbanColumn({
color={column.color}
count={column.rows.length}
onReorderDrop={onColumnReorder}
onHide={onHide}
/>
<div
ref={bodyRef}