Spaces:
Running
Running
Gowrisankar Cursor commited on
Commit ·
43462bc
1
Parent(s): 74fb13d
Fix allocation pill menu clicks and center bar labels.
Browse filesStop outside pointer handlers from swallowing menu actions and use flex alignment on pill bars.
Co-authored-by: Cursor <cursoragent@cursor.com>
frontend/src/components/projects/AllocationBarMenu.tsx
CHANGED
|
@@ -39,7 +39,7 @@ export function AllocationBarMenuTrigger({
|
|
| 39 |
setTransferOpen(false);
|
| 40 |
}
|
| 41 |
};
|
| 42 |
-
const
|
| 43 |
const target = event.target as Node;
|
| 44 |
if (triggerRef.current?.contains(target)) return;
|
| 45 |
if (menuRef.current?.contains(target)) return;
|
|
@@ -47,10 +47,13 @@ export function AllocationBarMenuTrigger({
|
|
| 47 |
setTransferOpen(false);
|
| 48 |
};
|
| 49 |
window.addEventListener("keydown", onKey);
|
| 50 |
-
window.
|
|
|
|
|
|
|
| 51 |
return () => {
|
|
|
|
| 52 |
window.removeEventListener("keydown", onKey);
|
| 53 |
-
window.removeEventListener("
|
| 54 |
};
|
| 55 |
}, [open]);
|
| 56 |
|
|
@@ -73,9 +76,11 @@ export function AllocationBarMenuTrigger({
|
|
| 73 |
setTransferOpen(false);
|
| 74 |
};
|
| 75 |
|
| 76 |
-
const run = (action: () => void) => {
|
| 77 |
-
|
|
|
|
| 78 |
close();
|
|
|
|
| 79 |
};
|
| 80 |
|
| 81 |
return (
|
|
@@ -96,17 +101,28 @@ export function AllocationBarMenuTrigger({
|
|
| 96 |
? createPortal(
|
| 97 |
<div
|
| 98 |
className="allocation-bar-menu"
|
|
|
|
|
|
|
| 99 |
ref={menuRef}
|
| 100 |
-
style={{ position: "fixed", top: anchor.top, left: Math.max(8, anchor.left), zIndex:
|
| 101 |
>
|
| 102 |
-
<button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
Edit
|
| 104 |
</button>
|
| 105 |
{onTransferPerson && transferPeople.length > 0 ? (
|
| 106 |
<>
|
| 107 |
<button
|
| 108 |
className="allocation-bar-menu-item"
|
| 109 |
-
onClick={() =>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
type="button"
|
| 111 |
>
|
| 112 |
Transfer
|
|
@@ -116,8 +132,12 @@ export function AllocationBarMenuTrigger({
|
|
| 116 |
<select
|
| 117 |
onChange={(event) => {
|
| 118 |
const id = Number(event.target.value);
|
| 119 |
-
if (id)
|
|
|
|
|
|
|
|
|
|
| 120 |
}}
|
|
|
|
| 121 |
value=""
|
| 122 |
>
|
| 123 |
<option value="">Choose person…</option>
|
|
@@ -132,14 +152,20 @@ export function AllocationBarMenuTrigger({
|
|
| 132 |
</>
|
| 133 |
) : null}
|
| 134 |
{onClone ? (
|
| 135 |
-
<button
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
Clone
|
| 137 |
</button>
|
| 138 |
) : null}
|
| 139 |
{onSelectAllToRight ? (
|
| 140 |
<button
|
| 141 |
className="allocation-bar-menu-item"
|
| 142 |
-
onClick={() => run(onSelectAllToRight)}
|
|
|
|
| 143 |
type="button"
|
| 144 |
>
|
| 145 |
Select All to Right
|
|
@@ -148,7 +174,8 @@ export function AllocationBarMenuTrigger({
|
|
| 148 |
{onToggleMultiSelect ? (
|
| 149 |
<button
|
| 150 |
className="allocation-bar-menu-item"
|
| 151 |
-
onClick={() => run(onToggleMultiSelect)}
|
|
|
|
| 152 |
type="button"
|
| 153 |
>
|
| 154 |
{multiSelectActive ? "Disable Multi-Select Mode" : "Enable Multi-Select Mode"}
|
|
@@ -157,7 +184,8 @@ export function AllocationBarMenuTrigger({
|
|
| 157 |
{onDelete ? (
|
| 158 |
<button
|
| 159 |
className="allocation-bar-menu-item is-danger"
|
| 160 |
-
onClick={() => run(onDelete)}
|
|
|
|
| 161 |
type="button"
|
| 162 |
>
|
| 163 |
Delete
|
|
|
|
| 39 |
setTransferOpen(false);
|
| 40 |
}
|
| 41 |
};
|
| 42 |
+
const onPointerDown = (event: PointerEvent) => {
|
| 43 |
const target = event.target as Node;
|
| 44 |
if (triggerRef.current?.contains(target)) return;
|
| 45 |
if (menuRef.current?.contains(target)) return;
|
|
|
|
| 47 |
setTransferOpen(false);
|
| 48 |
};
|
| 49 |
window.addEventListener("keydown", onKey);
|
| 50 |
+
const timer = window.setTimeout(() => {
|
| 51 |
+
window.addEventListener("pointerdown", onPointerDown);
|
| 52 |
+
}, 0);
|
| 53 |
return () => {
|
| 54 |
+
window.clearTimeout(timer);
|
| 55 |
window.removeEventListener("keydown", onKey);
|
| 56 |
+
window.removeEventListener("pointerdown", onPointerDown);
|
| 57 |
};
|
| 58 |
}, [open]);
|
| 59 |
|
|
|
|
| 76 |
setTransferOpen(false);
|
| 77 |
};
|
| 78 |
|
| 79 |
+
const run = (event: React.MouseEvent, action: () => void) => {
|
| 80 |
+
event.preventDefault();
|
| 81 |
+
event.stopPropagation();
|
| 82 |
close();
|
| 83 |
+
window.setTimeout(() => action(), 0);
|
| 84 |
};
|
| 85 |
|
| 86 |
return (
|
|
|
|
| 101 |
? createPortal(
|
| 102 |
<div
|
| 103 |
className="allocation-bar-menu"
|
| 104 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 105 |
+
onPointerDown={(event) => event.stopPropagation()}
|
| 106 |
ref={menuRef}
|
| 107 |
+
style={{ position: "fixed", top: anchor.top, left: Math.max(8, anchor.left), zIndex: 3500 }}
|
| 108 |
>
|
| 109 |
+
<button
|
| 110 |
+
className="allocation-bar-menu-item"
|
| 111 |
+
onClick={(event) => run(event, onEdit)}
|
| 112 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 113 |
+
type="button"
|
| 114 |
+
>
|
| 115 |
Edit
|
| 116 |
</button>
|
| 117 |
{onTransferPerson && transferPeople.length > 0 ? (
|
| 118 |
<>
|
| 119 |
<button
|
| 120 |
className="allocation-bar-menu-item"
|
| 121 |
+
onClick={(event) => {
|
| 122 |
+
event.stopPropagation();
|
| 123 |
+
setTransferOpen((value) => !value);
|
| 124 |
+
}}
|
| 125 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 126 |
type="button"
|
| 127 |
>
|
| 128 |
Transfer
|
|
|
|
| 132 |
<select
|
| 133 |
onChange={(event) => {
|
| 134 |
const id = Number(event.target.value);
|
| 135 |
+
if (id) {
|
| 136 |
+
close();
|
| 137 |
+
window.setTimeout(() => onTransferPerson(id), 0);
|
| 138 |
+
}
|
| 139 |
}}
|
| 140 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 141 |
value=""
|
| 142 |
>
|
| 143 |
<option value="">Choose person…</option>
|
|
|
|
| 152 |
</>
|
| 153 |
) : null}
|
| 154 |
{onClone ? (
|
| 155 |
+
<button
|
| 156 |
+
className="allocation-bar-menu-item"
|
| 157 |
+
onClick={(event) => run(event, onClone)}
|
| 158 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 159 |
+
type="button"
|
| 160 |
+
>
|
| 161 |
Clone
|
| 162 |
</button>
|
| 163 |
) : null}
|
| 164 |
{onSelectAllToRight ? (
|
| 165 |
<button
|
| 166 |
className="allocation-bar-menu-item"
|
| 167 |
+
onClick={(event) => run(event, onSelectAllToRight)}
|
| 168 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 169 |
type="button"
|
| 170 |
>
|
| 171 |
Select All to Right
|
|
|
|
| 174 |
{onToggleMultiSelect ? (
|
| 175 |
<button
|
| 176 |
className="allocation-bar-menu-item"
|
| 177 |
+
onClick={(event) => run(event, onToggleMultiSelect)}
|
| 178 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 179 |
type="button"
|
| 180 |
>
|
| 181 |
{multiSelectActive ? "Disable Multi-Select Mode" : "Enable Multi-Select Mode"}
|
|
|
|
| 184 |
{onDelete ? (
|
| 185 |
<button
|
| 186 |
className="allocation-bar-menu-item is-danger"
|
| 187 |
+
onClick={(event) => run(event, onDelete)}
|
| 188 |
+
onMouseDown={(event) => event.stopPropagation()}
|
| 189 |
type="button"
|
| 190 |
>
|
| 191 |
Delete
|
frontend/src/components/projects/AllocationDragTimeline.tsx
CHANGED
|
@@ -116,7 +116,7 @@ export function AllocationDragTimeline({
|
|
| 116 |
|
| 117 |
const onPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
| 118 |
if (!trackRef.current || event.button !== 0) return;
|
| 119 |
-
if ((event.target as HTMLElement).closest(".allocation-bar")) return;
|
| 120 |
dragPointerRef.current = { x: event.clientX, y: event.clientY };
|
| 121 |
const index = dayIndexFromPointer(event.clientX, trackRef.current.getBoundingClientRect(), totalDays);
|
| 122 |
setDragRange({ start: index, end: index });
|
|
@@ -189,14 +189,14 @@ export function AllocationDragTimeline({
|
|
| 189 |
) : null}
|
| 190 |
{existingSegment && weeklyCapacityHrs !== undefined && allocationPct !== undefined && allocation ? (
|
| 191 |
<div
|
| 192 |
-
className={`allocation-bar-host ${
|
| 193 |
ref={barHostRef}
|
| 194 |
style={{
|
| 195 |
...projectBarStyle(existingSegment, totalDays),
|
| 196 |
}}
|
| 197 |
>
|
| 198 |
<button
|
| 199 |
-
className="allocation-bar-main"
|
| 200 |
onClick={openEditPopover}
|
| 201 |
onPointerDown={(event) => event.stopPropagation()}
|
| 202 |
onPointerUp={(event) => event.stopPropagation()}
|
|
|
|
| 116 |
|
| 117 |
const onPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
| 118 |
if (!trackRef.current || event.button !== 0) return;
|
| 119 |
+
if ((event.target as HTMLElement).closest(".allocation-bar-host, .allocation-bar")) return;
|
| 120 |
dragPointerRef.current = { x: event.clientX, y: event.clientY };
|
| 121 |
const index = dayIndexFromPointer(event.clientX, trackRef.current.getBoundingClientRect(), totalDays);
|
| 122 |
setDragRange({ start: index, end: index });
|
|
|
|
| 189 |
) : null}
|
| 190 |
{existingSegment && weeklyCapacityHrs !== undefined && allocationPct !== undefined && allocation ? (
|
| 191 |
<div
|
| 192 |
+
className={`allocation-bar-host project-bar${visibleWeekdayCount(dayDates, allocation.start_date, allocation.end_date) <= 1 ? " is-pill" : " is-span"}${selected ? " is-selected" : ""}${cloneSourceId === allocation.id ? " is-clone-source" : ""}`}
|
| 193 |
ref={barHostRef}
|
| 194 |
style={{
|
| 195 |
...projectBarStyle(existingSegment, totalDays),
|
| 196 |
}}
|
| 197 |
>
|
| 198 |
<button
|
| 199 |
+
className="allocation-bar-main project-bar allocation-bar"
|
| 200 |
onClick={openEditPopover}
|
| 201 |
onPointerDown={(event) => event.stopPropagation()}
|
| 202 |
onPointerUp={(event) => event.stopPropagation()}
|
frontend/src/styles.css
CHANGED
|
@@ -2612,32 +2612,49 @@ th {
|
|
| 2612 |
}
|
| 2613 |
|
| 2614 |
.allocation-bar-host {
|
| 2615 |
-
align-items:
|
| 2616 |
display: flex;
|
| 2617 |
gap: 0;
|
| 2618 |
overflow: visible;
|
|
|
|
| 2619 |
z-index: 5;
|
| 2620 |
}
|
| 2621 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2622 |
.allocation-bar-host .allocation-bar-main {
|
|
|
|
| 2623 |
border: none;
|
| 2624 |
border-radius: inherit;
|
| 2625 |
color: inherit;
|
| 2626 |
cursor: pointer;
|
|
|
|
| 2627 |
flex: 1;
|
| 2628 |
font-family: inherit;
|
| 2629 |
-
font-size:
|
| 2630 |
-
font-weight:
|
| 2631 |
-
height:
|
|
|
|
| 2632 |
left: auto;
|
|
|
|
| 2633 |
min-width: 0;
|
| 2634 |
overflow: hidden;
|
| 2635 |
-
padding:
|
| 2636 |
position: relative;
|
| 2637 |
text-align: left;
|
| 2638 |
top: auto;
|
| 2639 |
}
|
| 2640 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2641 |
.allocation-bar-host.is-pill {
|
| 2642 |
border-radius: 999px;
|
| 2643 |
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.18);
|
|
@@ -2651,14 +2668,20 @@ th {
|
|
| 2651 |
.allocation-bar-host.is-pill .allocation-bar-main {
|
| 2652 |
border-radius: 999px 0 0 999px;
|
| 2653 |
color: #0f172a;
|
| 2654 |
-
height: 30px;
|
| 2655 |
padding: 0 4px 0 10px;
|
| 2656 |
}
|
| 2657 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2658 |
.allocation-bar-host.is-pill .allocation-bar-menu-btn {
|
|
|
|
| 2659 |
border-radius: 0 999px 999px 0;
|
| 2660 |
color: #1e40af;
|
| 2661 |
-
height:
|
| 2662 |
}
|
| 2663 |
|
| 2664 |
.allocation-bar-host.is-clone-source {
|
|
@@ -2692,6 +2715,7 @@ th {
|
|
| 2692 |
min-width: 220px;
|
| 2693 |
overflow: hidden;
|
| 2694 |
padding: 4px 0;
|
|
|
|
| 2695 |
}
|
| 2696 |
|
| 2697 |
.allocation-bar-menu-item {
|
|
@@ -3172,24 +3196,7 @@ th {
|
|
| 3172 |
z-index: 5;
|
| 3173 |
}
|
| 3174 |
|
| 3175 |
-
.
|
| 3176 |
-
border-radius: 999px;
|
| 3177 |
-
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.18);
|
| 3178 |
-
color: #0f172a;
|
| 3179 |
-
height: 30px;
|
| 3180 |
-
min-width: 44px;
|
| 3181 |
-
padding: 0 10px;
|
| 3182 |
-
top: 50%;
|
| 3183 |
-
transform: translateY(-50%);
|
| 3184 |
-
z-index: 6;
|
| 3185 |
-
}
|
| 3186 |
-
|
| 3187 |
-
.project-bar.allocation-bar.is-pill .planner-bar-label {
|
| 3188 |
-
color: #0f172a;
|
| 3189 |
-
font-size: 12px;
|
| 3190 |
-
font-weight: 800;
|
| 3191 |
-
padding: 0;
|
| 3192 |
-
}
|
| 3193 |
|
| 3194 |
.planner-row-archived {
|
| 3195 |
opacity: 0.55;
|
|
|
|
| 2612 |
}
|
| 2613 |
|
| 2614 |
.allocation-bar-host {
|
| 2615 |
+
align-items: center;
|
| 2616 |
display: flex;
|
| 2617 |
gap: 0;
|
| 2618 |
overflow: visible;
|
| 2619 |
+
position: absolute;
|
| 2620 |
z-index: 5;
|
| 2621 |
}
|
| 2622 |
|
| 2623 |
+
.allocation-bar-host.is-span {
|
| 2624 |
+
height: 36px;
|
| 2625 |
+
top: 14px;
|
| 2626 |
+
}
|
| 2627 |
+
|
| 2628 |
.allocation-bar-host .allocation-bar-main {
|
| 2629 |
+
align-items: center;
|
| 2630 |
border: none;
|
| 2631 |
border-radius: inherit;
|
| 2632 |
color: inherit;
|
| 2633 |
cursor: pointer;
|
| 2634 |
+
display: flex;
|
| 2635 |
flex: 1;
|
| 2636 |
font-family: inherit;
|
| 2637 |
+
font-size: 12px;
|
| 2638 |
+
font-weight: 700;
|
| 2639 |
+
height: 100%;
|
| 2640 |
+
justify-content: flex-start;
|
| 2641 |
left: auto;
|
| 2642 |
+
line-height: 1;
|
| 2643 |
min-width: 0;
|
| 2644 |
overflow: hidden;
|
| 2645 |
+
padding: 0 8px;
|
| 2646 |
position: relative;
|
| 2647 |
text-align: left;
|
| 2648 |
top: auto;
|
| 2649 |
}
|
| 2650 |
|
| 2651 |
+
.allocation-bar-host .allocation-bar-main .planner-bar-label {
|
| 2652 |
+
align-items: center;
|
| 2653 |
+
display: flex;
|
| 2654 |
+
line-height: 1.2;
|
| 2655 |
+
padding: 0;
|
| 2656 |
+
}
|
| 2657 |
+
|
| 2658 |
.allocation-bar-host.is-pill {
|
| 2659 |
border-radius: 999px;
|
| 2660 |
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.18);
|
|
|
|
| 2668 |
.allocation-bar-host.is-pill .allocation-bar-main {
|
| 2669 |
border-radius: 999px 0 0 999px;
|
| 2670 |
color: #0f172a;
|
|
|
|
| 2671 |
padding: 0 4px 0 10px;
|
| 2672 |
}
|
| 2673 |
|
| 2674 |
+
.allocation-bar-host.is-pill .allocation-bar-main .planner-bar-label {
|
| 2675 |
+
color: #0f172a;
|
| 2676 |
+
font-size: 12px;
|
| 2677 |
+
font-weight: 800;
|
| 2678 |
+
}
|
| 2679 |
+
|
| 2680 |
.allocation-bar-host.is-pill .allocation-bar-menu-btn {
|
| 2681 |
+
align-self: stretch;
|
| 2682 |
border-radius: 0 999px 999px 0;
|
| 2683 |
color: #1e40af;
|
| 2684 |
+
height: auto;
|
| 2685 |
}
|
| 2686 |
|
| 2687 |
.allocation-bar-host.is-clone-source {
|
|
|
|
| 2715 |
min-width: 220px;
|
| 2716 |
overflow: hidden;
|
| 2717 |
padding: 4px 0;
|
| 2718 |
+
pointer-events: auto;
|
| 2719 |
}
|
| 2720 |
|
| 2721 |
.allocation-bar-menu-item {
|
|
|
|
| 3196 |
z-index: 5;
|
| 3197 |
}
|
| 3198 |
|
| 3199 |
+
/* Pill layout handled by .allocation-bar-host.is-pill */
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3200 |
|
| 3201 |
.planner-row-archived {
|
| 3202 |
opacity: 0.55;
|