ERP-node/frontend/lib/registry/components/v2-timeline-scheduler/components/ScheduleBar.tsx

183 lines
5.4 KiB
TypeScript

"use client";
import React, { useState, useCallback, useRef } from "react";
import { cn } from "@/lib/utils";
import { ScheduleItem, ScheduleBarPosition, TimelineSchedulerConfig } from "../types";
import { statusOptions } from "../config";
interface ScheduleBarProps {
/** 스케줄 항목 */
schedule: ScheduleItem;
/** 위치 정보 */
position: ScheduleBarPosition;
/** 설정 */
config: TimelineSchedulerConfig;
/** 드래그 가능 여부 */
draggable?: boolean;
/** 리사이즈 가능 여부 */
resizable?: boolean;
/** 클릭 이벤트 */
onClick?: (schedule: ScheduleItem) => void;
/** 드래그 시작 */
onDragStart?: (schedule: ScheduleItem, e: React.MouseEvent) => void;
/** 드래그 중 */
onDrag?: (deltaX: number, deltaY: number) => void;
/** 드래그 종료 */
onDragEnd?: () => void;
/** 리사이즈 시작 */
onResizeStart?: (schedule: ScheduleItem, direction: "start" | "end", e: React.MouseEvent) => void;
/** 리사이즈 중 */
onResize?: (deltaX: number, direction: "start" | "end") => void;
/** 리사이즈 종료 */
onResizeEnd?: () => void;
}
export function ScheduleBar({
schedule,
position,
config,
draggable = true,
resizable = true,
onClick,
onDragStart,
onDragEnd,
onResizeStart,
onResizeEnd,
}: ScheduleBarProps) {
const [isDragging, setIsDragging] = useState(false);
const [isResizing, setIsResizing] = useState(false);
const barRef = useRef<HTMLDivElement>(null);
// 상태에 따른 색상
const statusColor = schedule.color ||
config.statusColors?.[schedule.status] ||
statusOptions.find((s) => s.value === schedule.status)?.color ||
"#3b82f6";
// 진행률 바 너비
const progressWidth = config.showProgress && schedule.progress !== undefined
? `${schedule.progress}%`
: "0%";
// 드래그 시작 핸들러
const handleMouseDown = useCallback(
(e: React.MouseEvent) => {
if (!draggable || isResizing) return;
e.preventDefault();
e.stopPropagation();
setIsDragging(true);
onDragStart?.(schedule, e);
const handleMouseMove = (moveEvent: MouseEvent) => {
// 드래그 중 로직은 부모에서 처리
};
const handleMouseUp = () => {
setIsDragging(false);
onDragEnd?.();
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
},
[draggable, isResizing, schedule, onDragStart, onDragEnd]
);
// 리사이즈 시작 핸들러
const handleResizeStart = useCallback(
(direction: "start" | "end", e: React.MouseEvent) => {
if (!resizable) return;
e.preventDefault();
e.stopPropagation();
setIsResizing(true);
onResizeStart?.(schedule, direction, e);
const handleMouseMove = (moveEvent: MouseEvent) => {
// 리사이즈 중 로직은 부모에서 처리
};
const handleMouseUp = () => {
setIsResizing(false);
onResizeEnd?.();
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
},
[resizable, schedule, onResizeStart, onResizeEnd]
);
// 클릭 핸들러
const handleClick = useCallback(
(e: React.MouseEvent) => {
if (isDragging || isResizing) return;
e.stopPropagation();
onClick?.(schedule);
},
[isDragging, isResizing, onClick, schedule]
);
return (
<div
ref={barRef}
className={cn(
"absolute rounded-md shadow-sm cursor-pointer transition-shadow",
"hover:shadow-md hover:z-10",
isDragging && "opacity-70 shadow-lg z-20",
isResizing && "z-20",
draggable && "cursor-grab",
isDragging && "cursor-grabbing"
)}
style={{
left: position.left,
top: position.top + 4,
width: position.width,
height: position.height - 8,
backgroundColor: statusColor,
}}
onClick={handleClick}
onMouseDown={handleMouseDown}
>
{/* 진행률 바 */}
{config.showProgress && schedule.progress !== undefined && (
<div
className="absolute inset-y-0 left-0 rounded-l-md opacity-30 bg-white"
style={{ width: progressWidth }}
/>
)}
{/* 제목 */}
<div className="relative z-10 px-2 py-1 text-xs text-white truncate font-medium">
{schedule.title}
</div>
{/* 진행률 텍스트 */}
{config.showProgress && schedule.progress !== undefined && (
<div className="absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-white/80 font-medium">
{schedule.progress}%
</div>
)}
{/* 리사이즈 핸들 - 왼쪽 */}
{resizable && (
<div
className="absolute left-0 top-0 bottom-0 w-2 cursor-ew-resize hover:bg-white/20 rounded-l-md"
onMouseDown={(e) => handleResizeStart("start", e)}
/>
)}
{/* 리사이즈 핸들 - 오른쪽 */}
{resizable && (
<div
className="absolute right-0 top-0 bottom-0 w-2 cursor-ew-resize hover:bg-white/20 rounded-r-md"
onMouseDown={(e) => handleResizeStart("end", e)}
/>
)}
</div>
);
}