e7e525df49
- 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>
90 lines
2.6 KiB
TypeScript
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>
|
|
);
|
|
}
|