mirror of
https://github.com/docmost/docmost.git
synced 2026-08-29 18:14:58 +08:00
feat(bases): hide and show kanban columns from header menu
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user