first commit
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
import { timeFormatter, timeRangeFormatter, TXCalendarEvent } from "@core/app/theme/scheduleView";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { DaySchedule } from "react-schedule-view";
|
||||
import style from "../../theme/scheduleView.module.css";
|
||||
import Avatar from "@components/Avatar/Avatar";
|
||||
|
||||
export interface CalendarRowViewProps {
|
||||
daySchedules: DaySchedule<TXCalendarEvent>[];
|
||||
handleEventClick: (event: TXCalendarEvent) => void;
|
||||
onEventLongPress?: (event: TXCalendarEvent) => void;
|
||||
}
|
||||
|
||||
type CalendarRowViewData = {
|
||||
name: string;
|
||||
events: TXCalendarEvent[];
|
||||
};
|
||||
|
||||
// Custom component to manage long press state per item
|
||||
function EventItem({
|
||||
item,
|
||||
onClick,
|
||||
onLongPress,
|
||||
}: {
|
||||
item: TXCalendarEvent;
|
||||
onClick: () => void;
|
||||
onLongPress?: () => void;
|
||||
}) {
|
||||
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const isLongPress = useRef(false);
|
||||
const isScrolling = useRef(false);
|
||||
const startPos = useRef({ x: 0, y: 0 });
|
||||
const [pressed, setPressed] = useState(false);
|
||||
|
||||
const getAvatar = (data: string | undefined) => {
|
||||
let imageAvatar = "";
|
||||
if (data) {
|
||||
imageAvatar = data;
|
||||
}
|
||||
return imageAvatar;
|
||||
};
|
||||
|
||||
const startPress = (e: React.TouchEvent<HTMLDivElement> | React.MouseEvent<HTMLDivElement>) => {
|
||||
isLongPress.current = false;
|
||||
isScrolling.current = false;
|
||||
if ('touches' in e) {
|
||||
startPos.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
|
||||
}
|
||||
setPressed(true);
|
||||
timerRef.current = setTimeout(() => {
|
||||
isLongPress.current = true;
|
||||
setPressed(false);
|
||||
if (window.navigator && window.navigator.vibrate) {
|
||||
window.navigator.vibrate(50); // Haptic feedback
|
||||
}
|
||||
if (onLongPress) {
|
||||
onLongPress();
|
||||
}
|
||||
}, 500);
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
|
||||
if (!isScrolling.current) {
|
||||
const moveX = e.touches[0].clientX;
|
||||
const moveY = e.touches[0].clientY;
|
||||
if (
|
||||
Math.abs(moveX - startPos.current.x) > 10 ||
|
||||
Math.abs(moveY - startPos.current.y) > 10
|
||||
) {
|
||||
isScrolling.current = true;
|
||||
cancelPress();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cancelPress = () => {
|
||||
setPressed(false);
|
||||
if (timerRef.current) {
|
||||
clearTimeout(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
cancelPress();
|
||||
if (!isLongPress.current && !isScrolling.current) {
|
||||
onClick();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
transform: pressed ? "scale(0.97)" : "scale(1)",
|
||||
transition: "transform 100ms ease",
|
||||
|
||||
cursor: "pointer",
|
||||
backgroundColor: item.color,
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
color: "var(--white)",
|
||||
fontWeight: "bold",
|
||||
marginLeft: "10px",
|
||||
marginRight: "10px",
|
||||
marginBottom: "5px",
|
||||
opacity: item.appointmentId ? 1 : 0.6,
|
||||
userSelect: "none",
|
||||
WebkitUserSelect: "none",
|
||||
}}
|
||||
onTouchStart={startPress}
|
||||
onTouchEnd={handleClick}
|
||||
onTouchMove={handleTouchMove}
|
||||
onTouchCancel={cancelPress}
|
||||
onMouseDown={startPress}
|
||||
onMouseUp={handleClick}
|
||||
onMouseLeave={cancelPress}
|
||||
onContextMenu={(e) => e.preventDefault()} // Prevent context menu on long press
|
||||
>
|
||||
<div>
|
||||
<div className={style.turnosXpressTitleContent}>
|
||||
<Avatar
|
||||
src={getAvatar(item.avatar)}
|
||||
alt={item.title}
|
||||
size="super-small"
|
||||
border="none"
|
||||
style={{ minWidth: "32px" }}
|
||||
/>
|
||||
<h1>{item.title}</h1>
|
||||
</div>
|
||||
<div className={style.turnosXpressTimeContent}>
|
||||
{timeRangeFormatter(item.startTime, item.endTime)}
|
||||
{item.payment && (
|
||||
<span style={{ marginLeft: "5px", marginTop: "5px" }}>💰</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CalendarRowView(props: CalendarRowViewProps) {
|
||||
const {
|
||||
daySchedules = [
|
||||
{
|
||||
name: "",
|
||||
events: [],
|
||||
},
|
||||
],
|
||||
handleEventClick = () => {},
|
||||
onEventLongPress,
|
||||
} = props;
|
||||
|
||||
const [data, setData] = useState<CalendarRowViewData[]>([]);
|
||||
|
||||
const getTimeHeader = (event: TXCalendarEvent): string => {
|
||||
return timeFormatter(event.startTime);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const auxData: CalendarRowViewData[] = [];
|
||||
for (const item of daySchedules[0].events) {
|
||||
const time = getTimeHeader(item);
|
||||
const check = auxData.find((x) => x.name === time);
|
||||
|
||||
if (check) {
|
||||
check.events.push(item);
|
||||
} else {
|
||||
auxData.push({
|
||||
name: time,
|
||||
events: [item],
|
||||
});
|
||||
}
|
||||
}
|
||||
setData(auxData.sort((a, b) => a.name.localeCompare(b.name)));
|
||||
}, [daySchedules]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.map((event: CalendarRowViewData) => {
|
||||
return (
|
||||
<div
|
||||
key={event.name}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
marginLeft: "5px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: "bold",
|
||||
color: "var(--gray-black)",
|
||||
width: "80px",
|
||||
}}
|
||||
>
|
||||
{event.name}
|
||||
</div>
|
||||
<div style={{ width: "100%" }}>
|
||||
{event.events.map((item: TXCalendarEvent) => (
|
||||
<EventItem
|
||||
key={item.appointmentId || item.repeatId}
|
||||
item={item}
|
||||
onClick={() => handleEventClick(item)}
|
||||
onLongPress={() => {
|
||||
if (onEventLongPress && item.appointmentId) {
|
||||
onEventLongPress(item);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user