Files
CapaKraken/apps/web/src/components/timeline/FloatingActionBar.tsx
T
Hartmut e7e525df49 fix(timeline): clear multi-select on drag start and lock in SSE edge-case coverage
- useTimelineDrag: onProjectBarMouseDown and single-alloc drag path now reset
  multiSelectRef + multiSelectState before starting a new drag, so the
  FloatingActionBar is dismissed immediately when an unrelated drag begins
- FloatingActionBar.test.tsx: 4 regression tests for the null-render guard
  (count=0) and all three label variants
- useTimelineSSE.test.ts: 2 new tests — tab hides during pending reconnect
  timer (clears timer, resyncs on next open) and first-ever connection fails
  before any open (retry open still resyncs correctly)
- assistant-tools-user-admin-inventory-read.test.ts: add isActive to expected
  findMany select shape (already in production, test was stale)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 21:16:10 +02:00

90 lines
2.6 KiB
TypeScript

"use client";
import React from "react";
import { clsx } from "clsx";
interface FloatingActionBarProps {
selectedAllocationCount: number;
selectedResourceCount: number;
onDelete: () => void;
onAssign: () => void;
onClear: () => void;
isDeleting: boolean;
}
export function FloatingActionBar({
selectedAllocationCount,
selectedResourceCount,
onDelete,
onAssign,
onClear,
isDeleting,
}: FloatingActionBarProps) {
const totalCount = selectedAllocationCount + selectedResourceCount;
if (totalCount === 0) return null;
const label =
selectedAllocationCount > 0 && selectedResourceCount > 0
? `${selectedAllocationCount} allocation${selectedAllocationCount !== 1 ? "s" : ""} + ${selectedResourceCount} resource${selectedResourceCount !== 1 ? "s" : ""} selected`
: selectedAllocationCount > 0
? `${selectedAllocationCount} allocation${selectedAllocationCount !== 1 ? "s" : ""} selected`
: `${selectedResourceCount} resource${selectedResourceCount !== 1 ? "s" : ""} selected`;
return (
<div
className={clsx(
"fixed bottom-6 left-1/2 -translate-x-1/2 z-50",
"flex items-center gap-3 rounded-full px-5 py-2.5",
"bg-white dark:bg-gray-800",
"border border-gray-200 dark:border-gray-700",
"shadow-xl dark:shadow-black/40",
)}
>
<span className="text-sm font-medium text-gray-700 dark:text-gray-200">
{label}
</span>
{selectedAllocationCount > 0 && (
<button
type="button"
onClick={onDelete}
disabled={isDeleting}
className={clsx(
"text-xs font-medium px-3 py-1.5 rounded-full transition-colors",
"bg-red-600 hover:bg-red-700 text-white",
"disabled:opacity-40 disabled:cursor-not-allowed",
)}
>
{isDeleting ? "Deleting\u2026" : "Delete"}
</button>
)}
{selectedResourceCount > 0 && (
<button
type="button"
onClick={onAssign}
className={clsx(
"text-xs font-medium px-3 py-1.5 rounded-full transition-colors",
"bg-sky-600 hover:bg-sky-700 dark:bg-sky-600 dark:hover:bg-sky-700 text-white",
)}
>
Assign
</button>
)}
<button
type="button"
onClick={onClear}
className={clsx(
"text-xs font-medium px-2 py-1.5 transition-colors",
"text-gray-500 dark:text-gray-400",
"hover:text-gray-700 dark:hover:text-gray-300",
)}
>
Clear{" "}
<span className="text-gray-400 dark:text-gray-500">(ESC)</span>
</button>
</div>
);
}