Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 35 additions & 20 deletions apps/web/src/components/bulk-selection/backlog-bulk-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,12 @@ function BacklogBulkToolbar() {
const { data: workspaceLabels = [] } = useGetLabelsByWorkspace(
workspace?.id ?? "",
);
const { canManageTasks, canAssignTasks } = useWorkspacePermission();
const canEdit = canManageTasks();
const { canUpdateTasks, canDeleteTasks, canAssignTasks, canUpdateLabels } =
useWorkspacePermission();
const canEdit = canUpdateTasks();
const canDelete = canDeleteTasks();
const canAssign = canAssignTasks();
const canEditLabels = canUpdateLabels();
const [isActionsOpen, setIsActionsOpen] = useState(false);
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);

Expand Down Expand Up @@ -261,27 +264,35 @@ function BacklogBulkToolbar() {

const groupedItems = useMemo<BacklogActionGroup[]>(() => {
const groups: BacklogActionGroup[] = [];
if (canEdit) {
if (canEdit || canDelete) {
groups.push({
value: "actions",
label: t("tasks:bulk.actions"),
items: [
{
value: "bulk-delete",
label: t("tasks:bulk.delete"),
icon: <Trash2 className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkDelete();
},
},
{
value: "bulk-archive",
label: t("tasks:bulk.archive"),
icon: <Archive className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkArchive();
},
},
...(canDelete
? [
{
value: "bulk-delete",
label: t("tasks:bulk.delete"),
icon: <Trash2 className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkDelete();
},
},
]
: []),
...(canEdit
? [
{
value: "bulk-archive",
label: t("tasks:bulk.archive"),
icon: <Archive className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkArchive();
},
},
]
: []),
],
});
}
Expand Down Expand Up @@ -322,6 +333,8 @@ function BacklogBulkToolbar() {
},
})),
});
}
if (canEditLabels) {
groups.push({
value: "label",
label: t("tasks:bulk.addLabel"),
Expand All @@ -347,7 +360,9 @@ function BacklogBulkToolbar() {
return groups;
}, [
canEdit,
canDelete,
canAssign,
canEditLabels,
workspaceUsers?.members,
uniqueLabels,
handleBulkDelete,
Expand All @@ -360,7 +375,7 @@ function BacklogBulkToolbar() {
]);

if (selectedCount === 0) return null;
if (!canEdit && !canAssign) return null;
if (!canEdit && !canDelete && !canAssign && !canEditLabels) return null;

return (
<div className="-translate-x-1/2 fixed bottom-6 left-1/2 z-50 transition-[translate,opacity] duration-200 ease-out starting:translate-y-3 starting:opacity-0 motion-reduce:starting:translate-y-0">
Expand Down
57 changes: 37 additions & 20 deletions apps/web/src/components/bulk-selection/bulk-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,12 @@ function BulkToolbar() {
const { data: workspaceLabels = [] } = useGetLabelsByWorkspace(
workspace?.id ?? "",
);
const { canManageTasks, canAssignTasks } = useWorkspacePermission();
const canEdit = canManageTasks();
const { canUpdateTasks, canDeleteTasks, canAssignTasks, canUpdateLabels } =
useWorkspacePermission();
const canEdit = canUpdateTasks();
const canDelete = canDeleteTasks();
const canAssign = canAssignTasks();
const canEditLabels = canUpdateLabels();
const [isActionsOpen, setIsActionsOpen] = useState(false);
const [isDatePickerOpen, setIsDatePickerOpen] = useState(false);

Expand Down Expand Up @@ -266,29 +269,39 @@ function BulkToolbar() {

const groupedItems = useMemo<BulkActionGroup[]>(() => {
const groups: BulkActionGroup[] = [];
if (canEdit) {
if (canEdit || canDelete) {
groups.push({
value: "actions",
label: t("tasks:bulk.actions"),
items: [
{
value: "bulk-delete",
label: t("tasks:bulk.delete"),
icon: <Trash2 className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkDelete();
},
},
{
value: "bulk-archive",
label: t("tasks:bulk.archive"),
icon: <Archive className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkArchive();
},
},
...(canDelete
? [
{
value: "bulk-delete",
label: t("tasks:bulk.delete"),
icon: <Trash2 className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkDelete();
},
},
]
: []),
...(canEdit
? [
{
value: "bulk-archive",
label: t("tasks:bulk.archive"),
icon: <Archive className="h-4 w-4 text-muted-foreground" />,
onRun: () => {
void handleBulkArchive();
},
},
]
: []),
],
});
}
if (canEdit) {
groups.push({
value: "status",
label: t("tasks:bulk.changeStatus"),
Expand Down Expand Up @@ -339,6 +352,8 @@ function BulkToolbar() {
},
})),
});
}
if (canEditLabels) {
groups.push({
value: "label",
label: t("tasks:bulk.addLabel"),
Expand All @@ -364,7 +379,9 @@ function BulkToolbar() {
return groups;
}, [
canEdit,
canDelete,
canAssign,
canEditLabels,
project?.columns,
workspaceUsers?.members,
uniqueLabels,
Expand All @@ -380,7 +397,7 @@ function BulkToolbar() {

if (selectedCount === 0) return null;
// Nothing the user can do in bulk → no toolbar.
if (!canEdit && !canAssign) return null;
if (!canEdit && !canDelete && !canAssign && !canEditLabels) return null;

return (
<div className="-translate-x-1/2 fixed bottom-6 left-1/2 z-50 transition-[translate,opacity] duration-200 ease-out starting:translate-y-3 starting:opacity-0 motion-reduce:starting:translate-y-0">
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/kanban-board/column/column-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export function ColumnHeader({ column }: ColumnHeaderProps) {
const { t } = useTranslation();
const { project, setProject } = useProjectStore();
const { mutate: updateTask } = useUpdateTask();
const { canManageTasks, canCreateTasks } = useWorkspacePermission();
const canTask = canManageTasks();
const { canUpdateTasks, canCreateTasks } = useWorkspacePermission();
const canTask = canUpdateTasks();
const canCreate = canCreateTasks();

const [isArchiveModalOpen, setIsArchiveModalOpen] = useState(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,10 @@ export default function TaskCardContextMenuContent({
const { mutateAsync: updateTaskTitle } = useUpdateTaskTitle();
const { mutateAsync: updateTaskDescription } = useUpdateTaskDescription();
const { mutateAsync: updateTaskDueDate } = useUpdateTaskDueDate();
const { canManageTasks, canAssignTasks } = useWorkspacePermission();
const canEdit = canManageTasks();
const { canUpdateTasks, canDeleteTasks, canAssignTasks } =
useWorkspacePermission();
const canEdit = canUpdateTasks();
const canDelete = canDeleteTasks();
const canAssign = canAssignTasks();

const usersOptions = useMemo(() => {
Expand Down Expand Up @@ -293,31 +295,43 @@ export default function TaskCardContextMenuContent({
</ContextMenuSub>
)}

{canEdit && (
{(canEdit || canDelete) && (
<>
<ContextMenuSeparator />
{canEdit && (
<>
<ContextMenuSeparator />

<ContextMenuItem onClick={() => handleChange("status", "archived")}>
<span>{t("tasks:actions.archive")}</span>
</ContextMenuItem>
<ContextMenuItem
onClick={() => handleChange("status", "archived")}
>
<span>{t("tasks:actions.archive")}</span>
</ContextMenuItem>

<ContextMenuItem onClick={() => handleChange("status", "planned")}>
<span>{t("tasks:actions.markAsPlanned")}</span>
</ContextMenuItem>
<ContextMenuItem
onClick={() => handleChange("status", "planned")}
>
<span>{t("tasks:actions.markAsPlanned")}</span>
</ContextMenuItem>
</>
)}

<ContextMenuSeparator />
{canDelete && (
<>
<ContextMenuSeparator />

<ContextMenuItem
className="text-destructive"
onClick={(e) => {
e.preventDefault();
setTimeout(() => {
onDeleteClick();
}, 0);
}}
>
<span>{t("tasks:actions.delete")}</span>
</ContextMenuItem>
<ContextMenuItem
className="text-destructive"
onClick={(e) => {
e.preventDefault();
setTimeout(() => {
onDeleteClick();
}, 0);
}}
>
<span>{t("tasks:actions.delete")}</span>
</ContextMenuItem>
</>
)}
</>
)}
</ContextMenuContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ vi.mock(
vi.mock("@/hooks/use-workspace-permission", () => ({
useWorkspacePermission: () => ({
canCreateTasks: () => true,
canManageLabels: () => true,
canCreateLabels: () => true,
}),
}));

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/shared/modals/create-task-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ function CreateTaskModal({
const { data: workspaceLabels = [] } = useGetLabelsByWorkspace(
workspace?.id || "",
);
const { canCreateTasks, canManageLabels } = useWorkspacePermission();
const { canCreateTasks, canCreateLabels } = useWorkspacePermission();
const canCreateTaskCapability = canCreateTasks();
const canCreateLabelCapability = canManageLabels();
const canCreateLabelCapability = canCreateLabels();

const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/task/subtask-status-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ export default function SubtaskStatusPopover({
isFinal: col.isFinal,
}));
const { mutateAsync: updateTaskStatus } = useUpdateTaskStatus();
const { canManageTasks } = useWorkspacePermission();
const canEdit = canManageTasks();
const { canUpdateTasks } = useWorkspacePermission();
const canEdit = canUpdateTasks();

const allSameStatus =
tasks.length > 0 && tasks.every((t) => t.status === tasks[0].status);
Expand Down
Loading
Loading