Gowrisankar Cursor commited on
Commit
43462bc
·
1 Parent(s): 74fb13d

Fix allocation pill menu clicks and center bar labels.

Browse files

Stop 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 onDown = (event: MouseEvent) => {
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.addEventListener("mousedown", onDown);
 
 
51
  return () => {
 
52
  window.removeEventListener("keydown", onKey);
53
- window.removeEventListener("mousedown", onDown);
54
  };
55
  }, [open]);
56
 
@@ -73,9 +76,11 @@ export function AllocationBarMenuTrigger({
73
  setTransferOpen(false);
74
  };
75
 
76
- const run = (action: () => void) => {
77
- action();
 
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: 1200 }}
101
  >
102
- <button className="allocation-bar-menu-item" onClick={() => run(onEdit)} type="button">
 
 
 
 
 
103
  Edit
104
  </button>
105
  {onTransferPerson && transferPeople.length > 0 ? (
106
  <>
107
  <button
108
  className="allocation-bar-menu-item"
109
- onClick={() => setTransferOpen((value) => !value)}
 
 
 
 
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) run(() => onTransferPerson(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 className="allocation-bar-menu-item" onClick={() => run(onClone)} type="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 ${existingSegment.className}${visibleWeekdayCount(dayDates, allocation.start_date, allocation.end_date) <= 1 ? " is-pill" : ""}${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"
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: stretch;
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: inherit;
2630
- font-weight: inherit;
2631
- height: auto;
 
2632
  left: auto;
 
2633
  min-width: 0;
2634
  overflow: hidden;
2635
- padding: inherit;
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: 30px;
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
- .project-bar.allocation-bar.is-pill {
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;