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

Show rich clone success toast above clone panel with View action.

Browse files

Raise toast z-index and use pushRich so cloned assignment feedback matches Runn.

Co-authored-by: Cursor <cursoragent@cursor.com>

frontend/src/components/projects/ProjectPlannerBlock.tsx CHANGED
@@ -298,7 +298,11 @@ export function ProjectPlannerBlock({
298
  (candidate) => candidate.is_active && candidate.id !== allocation.person_id,
299
  );
300
  return (
301
- <div className="planner-row planner-project-subrow planner-member-row" key={allocation.id}>
 
 
 
 
302
  <div className="planner-row-label nested member">
303
  <Avatar color={person.avatar_color} name={person.name} size={26} />
304
  <div className="person-meta">
 
298
  (candidate) => candidate.is_active && candidate.id !== allocation.person_id,
299
  );
300
  return (
301
+ <div
302
+ className="planner-row planner-project-subrow planner-member-row"
303
+ data-allocation-id={allocation.id}
304
+ key={allocation.id}
305
+ >
306
  <div className="planner-row-label nested member">
307
  <Avatar color={person.avatar_color} name={person.name} size={26} />
308
  <div className="person-meta">
frontend/src/pages/Projects.tsx CHANGED
@@ -113,6 +113,11 @@ export function ProjectsPage() {
113
  const [selectedAllocationIds, setSelectedAllocationIds] = useState<Set<number>>(new Set());
114
  const [cloneMode, setCloneMode] = useState<CloneModeContext | null>(null);
115
 
 
 
 
 
 
116
  const rangeWeeks = RANGE_OPTIONS.find((option) => option.value === range)?.weeks ?? 5;
117
  const startDate = anchor;
118
  const endDate = addWeeks(startDate, rangeWeeks);
@@ -201,6 +206,30 @@ export function ProjectsPage() {
201
  }, 150);
202
  }, []);
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  const createMutation = useMutation({
205
  mutationFn: createProject,
206
  onSuccess: (created) => {
@@ -355,31 +384,41 @@ export function ProjectsPage() {
355
  const cloneAllocationToPerson = async (personId: number) => {
356
  if (!cloneMode) return;
357
  const base = draftFromAllocation(cloneMode.allocation);
358
- await createAllocation({
359
- person_id: personId,
360
- project_id: cloneMode.projectId,
361
- start_date: base.start_date,
362
- end_date: base.end_date,
363
- allocation_pct: base.allocation_pct,
364
- note: base.note ?? null,
365
- });
366
- invalidate();
367
- push("Assignment cloned", "success");
 
 
 
 
 
368
  };
369
 
370
  const cloneAllocationToProject = async (targetProjectId: number) => {
371
  if (!cloneMode) return;
372
  const base = draftFromAllocation(cloneMode.allocation);
373
- await createAllocation({
374
- person_id: base.person_id,
375
- project_id: targetProjectId,
376
- start_date: base.start_date,
377
- end_date: base.end_date,
378
- allocation_pct: base.allocation_pct,
379
- note: base.note ?? null,
380
- });
381
- invalidate();
382
- push("Assignment cloned", "success");
 
 
 
 
 
383
  };
384
 
385
  const handleExtendAllocationRight = (projectId: number, allocation: Allocation) => {
 
113
  const [selectedAllocationIds, setSelectedAllocationIds] = useState<Set<number>>(new Set());
114
  const [cloneMode, setCloneMode] = useState<CloneModeContext | null>(null);
115
 
116
+ useEffect(() => {
117
+ document.body.classList.toggle("is-clone-mode-active", Boolean(cloneMode));
118
+ return () => document.body.classList.remove("is-clone-mode-active");
119
+ }, [cloneMode]);
120
+
121
  const rangeWeeks = RANGE_OPTIONS.find((option) => option.value === range)?.weeks ?? 5;
122
  const startDate = anchor;
123
  const endDate = addWeeks(startDate, rangeWeeks);
 
206
  }, 150);
207
  }, []);
208
 
209
+ const focusAllocation = useCallback((allocation: Allocation, projectId: number) => {
210
+ setExpandedProjectIds((prev) => new Set(prev).add(projectId));
211
+ window.setTimeout(() => {
212
+ document
213
+ .querySelector(`[data-allocation-id="${allocation.id}"]`)
214
+ ?.scrollIntoView({ block: "nearest", behavior: "smooth" });
215
+ }, 200);
216
+ }, []);
217
+
218
+ const showAssignmentClonedToast = useCallback(
219
+ (created: Allocation, subtitle: string, projectId: number) => {
220
+ pushRich({
221
+ title: "Assignment cloned",
222
+ subtitle,
223
+ tone: "success",
224
+ action: {
225
+ label: "View",
226
+ onClick: () => focusAllocation(created, projectId),
227
+ },
228
+ });
229
+ },
230
+ [focusAllocation, pushRich],
231
+ );
232
+
233
  const createMutation = useMutation({
234
  mutationFn: createProject,
235
  onSuccess: (created) => {
 
384
  const cloneAllocationToPerson = async (personId: number) => {
385
  if (!cloneMode) return;
386
  const base = draftFromAllocation(cloneMode.allocation);
387
+ try {
388
+ const created = await createAllocation({
389
+ person_id: personId,
390
+ project_id: cloneMode.projectId,
391
+ start_date: base.start_date,
392
+ end_date: base.end_date,
393
+ allocation_pct: base.allocation_pct,
394
+ note: base.note ?? null,
395
+ });
396
+ invalidate();
397
+ const personName = peopleById.get(personId)?.name ?? "Person";
398
+ showAssignmentClonedToast(created, personName, cloneMode.projectId);
399
+ } catch (error) {
400
+ push(toastFromError(error, "Could not clone assignment"), "error");
401
+ }
402
  };
403
 
404
  const cloneAllocationToProject = async (targetProjectId: number) => {
405
  if (!cloneMode) return;
406
  const base = draftFromAllocation(cloneMode.allocation);
407
+ try {
408
+ const created = await createAllocation({
409
+ person_id: base.person_id,
410
+ project_id: targetProjectId,
411
+ start_date: base.start_date,
412
+ end_date: base.end_date,
413
+ allocation_pct: base.allocation_pct,
414
+ note: base.note ?? null,
415
+ });
416
+ invalidate();
417
+ const projectName = projects.find((item) => item.id === targetProjectId)?.name ?? "Project";
418
+ showAssignmentClonedToast(created, projectName, targetProjectId);
419
+ } catch (error) {
420
+ push(toastFromError(error, "Could not clone assignment"), "error");
421
+ }
422
  };
423
 
424
  const handleExtendAllocationRight = (projectId: number, allocation: Allocation) => {
frontend/src/styles.css CHANGED
@@ -854,7 +854,11 @@ th {
854
  gap: 8px;
855
  position: fixed;
856
  right: 24px;
857
- z-index: 200;
 
 
 
 
858
  }
859
 
860
  .toast {
 
854
  gap: 8px;
855
  position: fixed;
856
  right: 24px;
857
+ z-index: 5000;
858
+ }
859
+
860
+ body.is-clone-mode-active .toast-stack {
861
+ bottom: calc(42vh + 20px);
862
  }
863
 
864
  .toast {