e7b74f13bd
- Extract shared render helpers (vacation blocks, range overlay, overbooking blink) into renderHelpers.tsx - Centralize status badge styles and vacation color maps into status-styles.ts - Extract dragMath.ts utility from useTimelineDrag for reuse - Split useInvalidatePlanningViews into useInvalidateTimeline (4 queries) + useInvalidatePlanningViews (8 queries) - Adopt findUniqueOrThrow() and Prisma select constants across API routers - Add shared fmtEur() helper for API-side money formatting - Wrap TimelineResourcePanel and TimelineProjectPanel with React.memo - Fix pre-existing TS2589 deep type errors in TeamCalendar and VacationModal - 38 files changed, reducing ~400 lines of duplicated code Co-Authored-By: claude-flow <ruv@ruv.net>
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
/**
|
|
* Pure math utilities for timeline drag operations.
|
|
* Extracted from useTimelineDrag to make the conversion logic testable
|
|
* and reusable across different drag modes.
|
|
*/
|
|
|
|
/** Convert a pixel delta to a number of whole days based on cell width. */
|
|
export function pixelsToDays(deltaX: number, cellWidth: number): number {
|
|
return Math.round(deltaX / cellWidth);
|
|
}
|
|
|
|
/**
|
|
* Shift a date by a given number of days, returning a new Date.
|
|
* Does not mutate the input.
|
|
*/
|
|
export function shiftDate(date: Date, daysDelta: number): Date {
|
|
const result = new Date(date);
|
|
result.setDate(result.getDate() + daysDelta);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Compute new start/end dates for a drag operation.
|
|
* Handles move, resize-start, and resize-end modes with clamping
|
|
* to prevent start from crossing past end (or vice versa).
|
|
*/
|
|
export function computeDragDates(
|
|
mode: "move" | "resize-start" | "resize-end",
|
|
originalStart: Date,
|
|
originalEnd: Date,
|
|
daysDelta: number,
|
|
): { start: Date; end: Date } {
|
|
const newStart = new Date(originalStart);
|
|
const newEnd = new Date(originalEnd);
|
|
|
|
if (mode === "move") {
|
|
newStart.setDate(newStart.getDate() + daysDelta);
|
|
newEnd.setDate(newEnd.getDate() + daysDelta);
|
|
} else if (mode === "resize-start") {
|
|
newStart.setDate(newStart.getDate() + daysDelta);
|
|
// Clamp: allow same-day but prevent crossing
|
|
if (newStart > newEnd) newStart.setTime(newEnd.getTime());
|
|
} else {
|
|
// resize-end
|
|
newEnd.setDate(newEnd.getDate() + daysDelta);
|
|
// Clamp: allow same-day but prevent crossing
|
|
if (newEnd < newStart) newEnd.setTime(newStart.getTime());
|
|
}
|
|
|
|
return { start: newStart, end: newEnd };
|
|
}
|