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 { useCallback, useMemo } from "react";
import { Badge } from "@mantine/core";
import { IconPlus } from "@tabler/icons-react";
import { import {
IBase, IBase,
IBaseRow, IBaseRow,
@@ -168,23 +170,91 @@ export function BaseKanban({
[base.id, columns, effectiveView, updateViewMutation], [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) { if (!isGroupable) {
return <KanbanEmptyState base={base} onPick={handlePickProperty} />; return <KanbanEmptyState base={base} onPick={handlePickProperty} />;
} }
return ( return (
<div className={classes.board}> <>
{columns.map((column) => ( {hiddenChoices.length > 0 && (
<KanbanColumn <div style={{ padding: "6px 12px", display: "flex", gap: 6, flexWrap: "wrap" }}>
key={column.key} {hiddenChoices.map((c) => (
column={column} <Badge
primaryProperty={primaryProperty} key={c.id}
onCardClick={onCardClick} color={c.color ?? "gray"}
onAddCard={handleAddCard} variant="outline"
onCardDrop={handleCardDrop} style={{ cursor: "pointer" }}
onColumnReorder={handleColumnReorder} onClick={() => handleShowColumn(c.id)}
/> rightSection={<IconPlus size={12} />}
))} >
</div> {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 { import {
useKanbanColumnReorder, useKanbanColumnReorder,
type ColumnReorderPayload, type ColumnReorderPayload,
@@ -12,6 +14,7 @@ type KanbanColumnHeaderProps = {
color: string | null; color: string | null;
count: number; count: number;
onReorderDrop: (payload: ColumnReorderPayload) => void; onReorderDrop: (payload: ColumnReorderPayload) => void;
onHide: (columnKey: string) => void;
}; };
export function KanbanColumnHeader({ export function KanbanColumnHeader({
@@ -20,7 +23,9 @@ export function KanbanColumnHeader({
color, color,
count, count,
onReorderDrop, onReorderDrop,
onHide,
}: KanbanColumnHeaderProps) { }: KanbanColumnHeaderProps) {
const { t } = useTranslation();
const { ref, isDragging, closestEdge } = useKanbanColumnReorder({ const { ref, isDragging, closestEdge } = useKanbanColumnReorder({
columnKey, columnKey,
onDrop: onReorderDrop, onDrop: onReorderDrop,
@@ -43,6 +48,21 @@ export function KanbanColumnHeader({
)} )}
<span className={classes.columnCount}>{count}</span> <span className={classes.columnCount}>{count}</span>
</div> </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} />} {closestEdge && <BaseDropEdgeIndicator edge={closestEdge} />}
</div> </div>
); );
@@ -15,6 +15,7 @@ type KanbanColumnProps = {
onAddCard: (columnKey: string) => void; onAddCard: (columnKey: string) => void;
onCardDrop: (payload: CardDropPayload) => void; onCardDrop: (payload: CardDropPayload) => void;
onColumnReorder: (payload: ColumnReorderPayload) => void; onColumnReorder: (payload: ColumnReorderPayload) => void;
onHide: (columnKey: string) => void;
}; };
export function KanbanColumn({ export function KanbanColumn({
@@ -24,6 +25,7 @@ export function KanbanColumn({
onAddCard, onAddCard,
onCardDrop, onCardDrop,
onColumnReorder, onColumnReorder,
onHide,
}: KanbanColumnProps) { }: KanbanColumnProps) {
const { ref: bodyRef, isOver } = useKanbanColumnDrop({ const { ref: bodyRef, isOver } = useKanbanColumnDrop({
columnKey: column.key, columnKey: column.key,
@@ -38,6 +40,7 @@ export function KanbanColumn({
color={column.color} color={column.color}
count={column.rows.length} count={column.rows.length}
onReorderDrop={onColumnReorder} onReorderDrop={onColumnReorder}
onHide={onHide}
/> />
<div <div
ref={bodyRef} ref={bodyRef}