UNPKG

@konnorkooi/schedule-glance

Version:
716 lines (682 loc) 31.6 kB
require("./index.css"); var $iEn1Z$reactjsxruntime = require("react/jsx-runtime"); var $iEn1Z$react = require("react"); var $iEn1Z$html2canvas = require("html2canvas"); function $parcel$exportWildcard(dest, source) { Object.keys(source).forEach(function(key) { if (key === 'default' || key === '__esModule' || Object.prototype.hasOwnProperty.call(dest, key)) { return; } Object.defineProperty(dest, key, { enumerable: true, get: function get() { return source[key]; } }); }); return dest; } function $parcel$export(e, n, v, s) { Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true}); } function $parcel$interopDefault(a) { return a && a.__esModule ? a.default : a; } $parcel$export(module.exports, "Schedule", () => $a93b004b8c50d707$export$2e2bcd8739ae039); $parcel$export(module.exports, "ScheduleHeader", () => $9d173187df8f14a9$export$2e2bcd8739ae039); $parcel$export(module.exports, "ScheduleCell", () => $32b595b0729c05b4$export$2e2bcd8739ae039); $parcel$export(module.exports, "EventPopup", () => $2c98468b9b21b1f5$export$2e2bcd8739ae039); const $5ae523ed11da3f14$export$d425309ca6befbaf = (events)=>{ return events.sort((a, b)=>{ // Convert time strings to comparable numbers const aTime = $5ae523ed11da3f14$export$6cd6be75c9d108e9(a.start); const bTime = $5ae523ed11da3f14$export$6cd6be75c9d108e9(b.start); return aTime - bTime; }); }; const $5ae523ed11da3f14$export$6cd6be75c9d108e9 = (time)=>{ const [hours, minutes] = time.split(":").map(Number); return hours * 60 + minutes; }; const $5ae523ed11da3f14$export$9a827e9be85de527 = (minutes)=>{ const hours = Math.floor(minutes / 60); const mins = minutes % 60; return `${hours.toString().padStart(2, "0")}:${mins.toString().padStart(2, "0")}`; }; const $5ae523ed11da3f14$export$51bd2919d2d38e79 = (events)=>{ if (!events.length) return { startHour: 9, endHour: 13 }; // Default business hours if no events let earliestStart = 1440; // Initialize to end of day let latestEnd = 0; events.forEach((event)=>{ const startMinutes = $5ae523ed11da3f14$export$6cd6be75c9d108e9(event.start); const endMinutes = $5ae523ed11da3f14$export$6cd6be75c9d108e9(event.end); if (startMinutes < earliestStart) earliestStart = startMinutes; if (endMinutes > latestEnd) latestEnd = endMinutes; }); // Round down to the nearest hour for start time const startHour = Math.max(0, Math.floor(earliestStart / 60)); // Round up to the next hour for end time and add 1 hour padding const endHour = Math.min(24, Math.ceil(latestEnd / 60)); // Ensure there's always at least 3 hours shown if (endHour - startHour < 3) return { startHour: Math.max(0, startHour - 1), endHour: Math.min(24, endHour + 1) }; return { startHour: startHour, endHour: endHour }; }; const $2c98468b9b21b1f5$var$EventPopup = ({ event: event, onClose: onClose })=>{ return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { className: "event-popup-overlay", style: { position: "fixed", top: 0, left: 0, right: 0, bottom: 0, margin: 0, padding: 0, width: "100vw", height: "100vh", backgroundColor: "rgba(0, 0, 0, 0.5)", zIndex: 9999, display: "flex", alignItems: "center", justifyContent: "center", pointerEvents: "auto" }, onClick: onClose, children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { className: "event-popup", style: { backgroundColor: "white", padding: "20px", borderRadius: "10px", boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)", maxWidth: "400px", width: "90%", position: "relative", maxHeight: "90vh", overflow: "auto", animation: "popupFadeIn 0.3s ease-out", transform: "none", margin: 0, left: "auto", right: "auto", top: "auto", bottom: "auto" // Remove any bottom positioning }, onClick: (e)=>e.stopPropagation(), children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { style: { marginBottom: "20px" }, children: [ event.title && /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("h3", { style: { margin: 0, fontSize: "18px", fontWeight: "bold", marginBottom: "10px", whiteSpace: "pre-wrap", wordBreak: "break-word", color: "#333" }, children: event.title }), event.body && /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("p", { style: { margin: "2px 0", fontSize: "14px", whiteSpace: "pre-wrap", wordBreak: "break-word", color: "#666", lineHeight: "1.5" }, children: event.body }), /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { style: { fontSize: "14px", color: "#666", marginTop: "10px", padding: "8px", backgroundColor: "#f5f5f5", borderRadius: "5px", display: "inline-block" }, children: [ event.start, " - ", event.end ] }) ] }), /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("button", { onClick: onClose, style: { backgroundColor: "#007bff", color: "white", border: "none", padding: "10px 20px", borderRadius: "5px", cursor: "pointer", transition: "all 0.2s ease-in-out", width: "100%", fontSize: "14px", fontWeight: "500" }, onMouseOver: (e)=>e.currentTarget.style.backgroundColor = "#0056b3", onMouseOut: (e)=>e.currentTarget.style.backgroundColor = "#007bff", children: "Close" }) ] }), /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("style", { children: ` @keyframes popupFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } .event-popup-overlay { position: fixed !important; left: 0 !important; top: 0 !important; width: 100vw !important; height: 100vh !important; display: flex !important; align-items: center !important; justify-content: center !important; } ` }) ] }); }; var $2c98468b9b21b1f5$export$2e2bcd8739ae039 = $2c98468b9b21b1f5$var$EventPopup; const $a93b004b8c50d707$var$Schedule = /*#__PURE__*/ (0, $iEn1Z$react.forwardRef)(({ events: initialEvents, onEventClick: onEventClick, headers: headers = [ { label: "Monday", dayIndex: 0 }, { label: "Tuesday", dayIndex: 1 }, { label: "Wednesday", dayIndex: 2 }, { label: "Thursday", dayIndex: 3 }, { label: "Friday", dayIndex: 4 } ], customPopupHandler: customPopupHandler, useDefaultPopup: useDefaultPopup = true, emptyStateMessage: emptyStateMessage = "No events scheduled" }, ref)=>{ const [events, setEvents] = (0, $iEn1Z$react.useState)(initialEvents); const [selectedEvent, setSelectedEvent] = (0, $iEn1Z$react.useState)(null); const [startHour, setStartHour] = (0, $iEn1Z$react.useState)(8); const [endHour, setEndHour] = (0, $iEn1Z$react.useState)(18); const scheduleRef = (0, $iEn1Z$react.useRef)(null); const [isExporting, setIsExporting] = (0, $iEn1Z$react.useState)(false); const [windowWidth, setWindowWidth] = (0, $iEn1Z$react.useState)(window.innerWidth); (0, $iEn1Z$react.useEffect)(()=>{ const handleResize = ()=>setWindowWidth(window.innerWidth); window.addEventListener("resize", handleResize); return ()=>window.removeEventListener("resize", handleResize); }, []); (0, $iEn1Z$react.useEffect)(()=>{ setEvents(initialEvents); // Add padding to start and end hours to prevent tight fit const { startHour: newStartHour, endHour: newEndHour } = (0, $5ae523ed11da3f14$export$51bd2919d2d38e79)(initialEvents); setStartHour(Math.max(0, newStartHour)); // could Add 1 hour padding at start -1 setEndHour(Math.min(24, newEndHour)); // could Add 1 hour padding at end +1 }, [ initialEvents ]); // Expose the export function via ref (0, $iEn1Z$react.useImperativeHandle)(ref, ()=>({ exportToPng: async (filename = "schedule-export.png")=>{ if (!scheduleRef.current) { console.error("Schedule ref is null"); return; } setIsExporting(true); try { console.log("Starting export..."); const canvas = await (0, ($parcel$interopDefault($iEn1Z$html2canvas)))(scheduleRef.current, { backgroundColor: "#ffffff", scale: 2, logging: true, useCORS: true, allowTaint: true }); console.log("Canvas generated, creating download..."); canvas.toBlob((blob)=>{ if (blob) { const url = window.URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); window.URL.revokeObjectURL(url); console.log("Download initiated"); } else console.error("Failed to create blob"); }, "image/png", 1.0); } catch (error) { console.error("Failed to export PNG:", error); throw error; // Re-throw to allow error handling by the consumer } finally{ setIsExporting(false); } } })); const handleEventClick = (event)=>{ if (customPopupHandler) customPopupHandler(event); else if (useDefaultPopup) setSelectedEvent(event); if (onEventClick) onEventClick(event); }; const formatHour = (hour)=>{ const period = hour >= 12 ? "PM" : "AM"; const displayHour = hour % 12 || 12; return `${displayHour}:00 ${period}`; }; // Rest of the existing renderEventContent function remains the same // In src/components/Schedule.tsx // Update the renderEventContent function: // In src/components/Schedule.tsx // Update the renderEventContent function: const renderEventContent = (event)=>{ // Determine if we're on mobile const isMobile = windowWidth <= 600; const isExtraSmallMobile = windowWidth <= 480; // Set font sizes based on screen size (only for event content) const titleSize = isExtraSmallMobile ? "10px" : isMobile ? "11px" : "14px"; const bodySize = isExtraSmallMobile ? "8px" : isMobile ? "9px" : "12px"; const timeSize = isExtraSmallMobile ? "7px" : isMobile ? "8px" : "10px"; if (event.customContent) { // For custom HTML content events if (isMobile) { // Only modify custom content on mobile const mobileCustomContent = event.customContent.replace(/font-size:\s*\d+px/g, (match)=>{ // Reduce any explicit font sizes const currentSize = parseInt(match.replace(/[^0-9]/g, "")); const newSize = Math.floor(currentSize * 0.8); // Reduce by 30% return `font-size: ${newSize}px`; }); return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { dangerouslySetInnerHTML: { __html: mobileCustomContent } }); } return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { dangerouslySetInnerHTML: { __html: event.customContent } }); } if (event.title || event.body) return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { className: "event-content", style: { padding: "5px", height: "100%" }, children: [ event.title && /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("h3", { style: { margin: 0, fontSize: titleSize, fontWeight: "bold", whiteSpace: "pre-wrap", overflow: "hidden", display: "-webkit-box", WebkitLineClamp: isMobile ? "1" : "2", WebkitBoxOrient: "vertical", lineHeight: "1.2" }, children: event.title }), event.body && /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("p", { style: { margin: "2px 0", fontSize: bodySize, whiteSpace: "pre-wrap", overflow: "hidden", display: "-webkit-box", WebkitLineClamp: isMobile ? "1" : "3", WebkitBoxOrient: "vertical", lineHeight: "1.2" }, children: event.body }), /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { style: { fontSize: timeSize, color: "#666", marginTop: "2px" }, children: [ event.start, " - ", event.end ] }) ] }); return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { className: "event-content", style: { padding: "5px" }, children: /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { style: { fontSize: timeSize }, children: [ event.start, " - ", event.end ] }) }); }; const groupEventsByDay = (events)=>{ const grouped = new Map(); headers.forEach((header)=>{ grouped.set(header.dayIndex, []); }); events.forEach((event)=>{ if (Array.isArray(event.days)) event.days.forEach((dayIndex)=>{ if (grouped.has(dayIndex)) grouped.get(dayIndex).push(event); }); }); return grouped; }; const groupedEvents = groupEventsByDay((0, $5ae523ed11da3f14$export$d425309ca6befbaf)(events)); const timeSlots = Array.from({ length: endHour - startHour }, (_, i)=>formatHour(i + startHour)); const timeToMinutes = (time)=>{ const [hours, minutes] = time.split(":").map(Number); return hours * 60 + minutes; }; const calculateEventPosition = (event)=>{ const totalMinutes = (endHour - startHour) * 60; const eventStartMinutes = timeToMinutes(event.start) - startHour * 60; const eventEndMinutes = timeToMinutes(event.end) - startHour * 60; // Ensure positions are within bounds const top = Math.max(0, Math.min(100, eventStartMinutes / totalMinutes * 100)); const height = Math.max(0, Math.min(100 - top, (eventEndMinutes - eventStartMinutes) / totalMinutes * 100)); return { top: top, height: height }; }; const renderTimeLines = ()=>Array.from({ length: endHour - startHour }, (_, i)=>/*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { className: "time-line", style: { position: "absolute", left: 0, right: 0, top: `${i * 100 / (endHour - startHour)}%`, borderTop: "1px solid #e5e5e5", width: "100%", pointerEvents: "none", zIndex: 1 } }, `timeline-${i}`)); const borderSpacing = "0.625rem"; // 10px equivalent const timeColumnWidth = "5rem"; // 80px equivalent const hasEvents = events.length > 0; return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { className: "schedule", ref: scheduleRef, style: { width: "100%", height: "100%", position: "absolute", inset: 0 }, children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("table", { className: "schedule-table", style: { width: "100%", height: "100%", borderCollapse: "separate", borderSpacing: borderSpacing, backgroundColor: "#f0f0f0", borderRadius: "20px", tableLayout: "fixed" }, children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("colgroup", { children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("col", { style: { width: timeColumnWidth } }), headers.map((_, index)=>/*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("col", {}, index)) ] }), /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("thead", { children: /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("tr", { style: { height: "3rem" }, children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("th", { className: "time-header", children: "Time" }), headers.map((header, index)=>/*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("th", { children: header.label }, index)) ] }) }), /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("tbody", { children: /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("tr", { style: { height: "calc(100% - 3rem)" }, children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("td", { className: "time-column", style: { position: "relative", backgroundColor: "#ffffff", borderRadius: "10px", padding: 0, verticalAlign: "top", width: timeColumnWidth }, children: /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, display: "flex", flexDirection: "column", height: "100%", paddingTop: "0.15625rem" }, children: timeSlots.map((time, index)=>/*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { className: "time-slot", style: { flex: `1 0 ${100 / timeSlots.length}%`, display: "flex", alignItems: "flex-start", justifyContent: "center", fontSize: "0.85em", color: "#666", boxSizing: "border-box", position: "relative", height: `${100 / timeSlots.length}%`, borderBottom: index < timeSlots.length - 1 ? "1px solid #e5e5e5" : "none" }, children: /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("span", { style: { position: "absolute", top: 0, paddingTop: "0.3125rem" }, children: time }) }, index)) }) }), headers.map((header)=>{ const dayEvents = groupedEvents.get(header.dayIndex) || []; return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("td", { className: "day-column", style: { position: "relative", backgroundColor: "#ffffff", borderRadius: "10px", padding: 0, verticalAlign: "top" }, children: [ renderTimeLines(), dayEvents.map((event)=>{ const { top: top, height: height } = calculateEventPosition(event); return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { className: "schedule-event", style: { backgroundColor: event.color || "#e0e0e0", top: `${top}%`, height: `${height}%`, minHeight: "1.25rem", position: "absolute", left: "0.3125rem", right: "0.3125rem", zIndex: 2, borderRadius: "5px", cursor: "pointer", overflow: "hidden", transition: "transform 0.2s ease-in-out", fontSize: "0.8em" }, onClick: ()=>handleEventClick(event), children: renderEventContent(event) }, event.id); }) ] }, header.dayIndex); }) ] }) }) ] }), !hasEvents && /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { style: { position: "absolute", top: 0, left: 0, right: 0, bottom: 0, backgroundColor: "rgba(128, 128, 128, 0.1)", backdropFilter: "blur(2px)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1000, borderRadius: "20px" }, children: /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { style: { backgroundColor: "white", padding: "20px 40px", borderRadius: "10px", boxShadow: "0 2px 10px rgba(0, 0, 0, 0.1)", textAlign: "center" }, children: /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("p", { style: { margin: 0, fontSize: "1.1em", color: "#666", fontWeight: 500 }, children: emptyStateMessage }) }) }), selectedEvent && /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)((0, $2c98468b9b21b1f5$export$2e2bcd8739ae039), { event: selectedEvent, onClose: ()=>setSelectedEvent(null) }) ] }); }); // Close the forwardRef callback var $a93b004b8c50d707$export$2e2bcd8739ae039 = $a93b004b8c50d707$var$Schedule; // ScheduleHeader component renders the header row of the schedule const $9d173187df8f14a9$var$ScheduleHeader = ({ headers: headers })=>{ return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { className: "schedule-header", children: headers.map((header, index)=>/*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { className: "header-cell", children: header }, index)) }); }; var $9d173187df8f14a9$export$2e2bcd8739ae039 = $9d173187df8f14a9$var$ScheduleHeader; // ScheduleCell component renders events within a cell of the Schedule const $32b595b0729c05b4$var$ScheduleCell = ({ events: events, onEventClick: onEventClick })=>{ return /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("div", { className: "schedule-cell", children: events.map((event)=>/*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("div", { className: "schedule-event", style: { backgroundColor: event.color }, onClick: ()=>onEventClick(event), children: [ /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsx)("span", { className: "event-title", children: event.title }), /*#__PURE__*/ (0, $iEn1Z$reactjsxruntime.jsxs)("span", { className: "event-time", children: [ event.start, " - ", event.end ] }) ] }, event.id)) }); }; var $32b595b0729c05b4$export$2e2bcd8739ae039 = $32b595b0729c05b4$var$ScheduleCell; var $0a52e09d9b2e0488$exports = {}; console.log("Schedule CSS imported"); // Add this line if not already present $parcel$exportWildcard(module.exports, $0a52e09d9b2e0488$exports);