UNPKG

@san.devv/sandev-react-calendar

Version:

A **highly customizable React calendar component** designed for productivity apps, attendance tracking, and event management. It features **dynamic day/week views**, time overlays, and modal integrations — all built using **React, TypeScript, Bootstrap**,

260 lines (251 loc) 24.5 kB
import { jsxs, Fragment, jsx } from 'react/jsx-runtime'; import { useState, useMemo } from 'react'; import { startOfMonth, endOfMonth, eachDayOfInterval, startOfWeek, endOfWeek, isSameDay, isSameWeek, isSameMonth, format, addDays, parseISO, subMonths, subWeeks, addMonths, addWeeks, isWithinInterval, endOfDay, startOfDay } from 'date-fns'; import { OverlayTrigger, Tooltip } from 'react-bootstrap'; /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var Calendar = function (_a) { _a.children; var events = _a.events, setSelectedEvent = _a.setSelectedEvent, setShowEventModal = _a.setShowEventModal; var _b = useState(new Date()), currentDate = _b[0], setCurrentDate = _b[1]; var _c = useState("month"), viewMode = _c[0], setViewMode = _c[1]; // Navigation functions var prevPeriod = function () { if (viewMode === "month") setCurrentDate(subMonths(currentDate, 1)); if (viewMode === "week") setCurrentDate(subWeeks(currentDate, 1)); if (viewMode === "day") setCurrentDate(addDays(currentDate, -1)); }; var nextPeriod = function () { if (viewMode === "month") setCurrentDate(addMonths(currentDate, 1)); if (viewMode === "week") setCurrentDate(addWeeks(currentDate, 1)); if (viewMode === "day") setCurrentDate(addDays(currentDate, 1)); }; var goToToday = function () { return setCurrentDate(new Date()); }; // Date calculations var monthStart = startOfMonth(currentDate); var monthEnd = endOfMonth(currentDate); eachDayOfInterval({ start: monthStart, end: monthEnd }); var weekStart = startOfWeek(currentDate); var weekEnd = endOfWeek(currentDate); eachDayOfInterval({ start: weekStart, end: weekEnd }); var getDayEvents = function (day) { return events.filter(function (event) { var eventDate = parseISO(event.date); var eventEndDate = event.endDate ? parseISO(event.endDate) : eventDate; // Check if the day is within the event's date range return isWithinInterval(day, { start: startOfDay(eventDate), end: endOfDay(eventEndDate), }); }); }; var getDayColor = function (date) { var event = events.find(function (event) { return event.date === date; }); return event ? event.dayColor : "bg-default"; }; // Time slots for day view var timeSlots = useMemo(function () { return Array.from({ length: 24 }, function (_, i) { var hour = i % 12 || 12; var period = i < 12 ? "AM" : "PM"; return "".concat(hour, ":00 ").concat(period); }); }, []); // Handle event click var handleEventClick = function (event) { // alert(event.id) setSelectedEvent(event); setShowEventModal(true); }; function isEventOnDay(event, day) { var eventStart = parseISO(event.date); var eventEnd = event.endDate ? parseISO(event.endDate) : eventStart; return day >= eventStart && day <= eventEnd; } var timeToDecimal = function (t) { if (!t) return 0; var _a = t.split(":").map(Number), hours = _a[0], minutes = _a[1]; return hours + minutes / 60; }; var getWeekDates = function (date) { var start = startOfWeek(date, { weekStartsOn: 0 }); // 0 = Sunday return Array.from({ length: 7 }, function (_, i) { return addDays(start, i); }); }; return (jsxs(Fragment, { children: [jsx("div", { className: "pb-4", children: jsxs("div", { className: "d-flex align-items-center justify-content-between g-3", children: [jsxs("div", { className: "col-auto d-flex gap-2 border border-gray-400 rounded p-1", children: [jsx("button", { onClick: prevPeriod, className: "btn btn-sm btn-outline-secondary p-1 px-2", title: "Previous ".concat(viewMode), children: "Previous" }), jsx("button", { onClick: goToToday, className: "btn btn-sm btn-outline-secondary bg-".concat(viewMode === "day" && isSameDay(currentDate, new Date()) ? "primary text-gray-100" : viewMode === "week" && isSameWeek(currentDate, new Date()) ? "primary text-gray-100" : viewMode === "month" && isSameMonth(currentDate, new Date()) ? "primary text-gray-100" : "", " p-1 px-2"), children: viewMode === "day" ? "Today" : viewMode === "week" ? "This Week" : "This Month" }), jsx("button", { onClick: nextPeriod, className: "btn btn-sm btn-outline-secondary p-1 px-2", title: "Next ".concat(viewMode), children: "Next" })] }), jsx("div", { className: "col-auto p-0", children: jsx("input", { type: "month", className: "form-control p-1 border border-gray-400 ", value: format(currentDate, "yyyy-MM"), onChange: function (e) { if (!e.target.value) return alert("⚠️ Clear is not allowed 🚫"); var _a = e.target.value.split("-"), year = _a[0], month = _a[1]; setCurrentDate(new Date(Number(year), Number(month) - 1, new Date().getDate())); } }) }), jsxs("div", { className: "col-auto d-flex gap-2 border border-gray-400 rounded p-1", children: [jsx("button", { onClick: function () { setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth(), new Date().getDate())); setViewMode("day"); }, className: "btn btn-sm p-1 px-2 ".concat(viewMode === "day" ? "btn-primary" : "btn-outline-secondary"), children: "Day" }), jsx("button", { onClick: function () { return setViewMode("week"); }, className: "btn btn-sm p-1 px-2 ".concat(viewMode === "week" ? "btn-primary" : "btn-outline-secondary"), children: "Week" }), jsx("button", { onClick: function () { return setViewMode("month"); }, className: "btn btn-sm p-1 px-2 ".concat(viewMode === "month" ? "btn-primary" : "btn-outline-secondary"), children: "Month" })] })] }) }), viewMode === "month" && (jsxs("div", { className: "d-flex flex-column border border-gray-400 rounded p-1", children: [jsx("div", { className: "row row-cols-7 g-0", children: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"].map(function (day) { return (jsx("div", { className: "col text-center fw-semibold bg-light py-2 border border-gray-400 rounded", children: day }, day)); }) }), Array.from({ length: 6 }).map(function (_, weekIndex) { return (jsx("div", { className: "row row-cols-7 g-0", children: Array.from({ length: 7 }).map(function (_, dayIndex) { var day = addDays(startOfWeek(monthStart), weekIndex * 7 + dayIndex); var dayEvents = events.filter(function (event) { return isEventOnDay(event, day); }); var eventsToShow = dayEvents.slice(0, 3); // Show max 3 events var hasMoreEvents = dayEvents.length > 3; return (jsxs("div", { className: "col border border-gray-400 rounded p-2 ".concat(!isSameMonth(day, currentDate) ? "opacity-50" : ""), style: { minHeight: "100px", width: "50px", backgroundColor: getDayColor(format(day, "yyyy-MM-dd")) !== "bg-default" ? getDayColor(format(day, "yyyy-MM-dd")) : "transparent", }, children: [jsx("div", { className: "d-flex justify-content-between", children: jsx("button", { onClick: function () { // alert(getDayColor(format(day, "yyyy-MM-dd"))); setCurrentDate(new Date(currentDate.getFullYear(), day.getMonth(), Number(format(day, "d")))); setViewMode("day"); }, className: "d-flex fw-bold small cursor-pointer rounded-circle justify-content-center align-items-center ".concat(isSameDay(day, new Date()) ? " bg-primary text-white" : " bg-hover-light-danger").concat(isSameMonth(day, currentDate) ? " border border-hover-primary" : " border-0"), style: { width: "24px", height: "24px" }, disabled: !isSameMonth(day, currentDate), children: format(day, "d") }) }), jsxs("div", { className: "mt-2", children: [eventsToShow.map(function (event) { return (jsx(Fragment, { children: event.type !== "attendance" ? (jsx("div", { onClick: function () { return handleEventClick(event); }, className: "badge d-block text-truncate my-1 text-white cursor-pointer ".concat(event.badgeColor), style: { fontSize: "0.75rem" }, children: event.title }, "".concat(event.id, "-").concat(day.toDateString()))) : (jsxs("div", { className: "mt-2 cursor-pointer rounded", onClick: function () { return handleEventClick(event); }, children: [jsxs("div", { className: "row w-100 m-0 gap-1 mb-1", children: [jsx("div", { className: "col-sm text-center p-1 fw-bold text-gray-900 fs-7 rounded border border-gray-700", style: { fontSize: "0.7rem" }, children: event.inTime }), jsx("div", { className: "col-sm text-center p-1 fw-bold text-gray-900 fs-7 rounded border border-gray-700", style: { fontSize: "0.7rem" }, children: event.outTime })] }), jsx("div", { className: "text-center p-1 fw-bold text-gray-900 fs-7 rounded border border-gray-700 mb-1", style: { fontSize: "0.7rem" }, children: event.totalHrs }), jsx("div", { className: "text-center p-1 fw-bold text-gray-900 fs-7 rounded border border-gray-700 mb-1", style: { fontSize: "0.7rem" }, children: event.description })] })) })); }), hasMoreEvents && (jsx("div", { className: "badge d-block text-truncate my-1 text-gray-700 cursor-pointer border border-gray-600", style: { fontSize: "0.75rem" }, onClick: function () { setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth(), Number(format(day, "d")))); setViewMode("day"); }, children: "View More..." }))] })] }, dayIndex)); }) }, weekIndex)); })] })), viewMode === "week" && (jsx("div", { className: "schedule-view border border-gray-400 rounded p-1 overflow-auto", children: jsxs("div", { className: "d-flex border border-gray-400 flex-column", style: { minWidth: "100%" }, children: [jsxs("div", { className: "d-flex border-bottom border-gray-400 bg-light", children: [jsx("div", { className: "border-end border-gray-400 text-center fw-semibold py-2", style: { width: "120px" }, children: "Time" }), getWeekDates(currentDate).map(function (day, i) { return (jsxs("div", { className: "border-end border-gray-400 text-center fw-semibold py-2", style: { minWidth: "120px", flexGrow: 1 }, children: [jsx("div", { children: format(day, "EEE") }), jsx("div", { className: "small cursor-pointer border border-hover-primary rounded-circle mx-auto ".concat(isSameDay(day, new Date()) ? "bg-primary text-white" : "mx-auto"), style: { width: "24px", height: "24px", lineHeight: "24px", }, onClick: function () { setCurrentDate(new Date(currentDate.getFullYear(), day.getMonth(), Number(format(day, "d")))); setViewMode("day"); }, children: format(day, "d") })] }, i)); })] }), jsxs("div", { className: "d-flex", children: [jsx("div", { className: "border-end border-gray-400 bg-light", style: { width: "120px" }, children: timeSlots.map(function (time, i) { return (jsx("div", { className: "text-end small py-2 pe-2 border-bottom border-gray-400 text-center h-60px", children: time }, i)); }) }), getWeekDates(currentDate).map(function (day, dayIndex) { return (jsx("div", { className: "border-end border-gray-400 position-relative", style: { minWidth: "120px", height: timeSlots.length * 60, flexGrow: 1, }, children: jsx("div", { className: "position-absolute top-0 start-0 w-100 h-100", children: getDayEvents(day).map(function (event, index) { // Determine if this is a multi-day event var isMultiDay = event.endDate && !isSameDay(parseISO(event.date), parseISO(event.endDate)); // Calculate start/end times for this specific day var startTime = "00:00"; var endTime = "23:59"; // For single-day events or first day of multi-day events if ((!isMultiDay && event.startTime) || event.inTime || (isMultiDay && isSameDay(day, parseISO(event.date)) && event.startTime) || event.inTime || "") { startTime = event.startTime || event.inTime || ""; } // For single-day events or last day of multi-day events if ((!isMultiDay && event.endTime) || event.outTime || (isMultiDay && isSameDay(day, parseISO(event.endDate || event.outTime || "")) && event.endTime) || event.outTime || "") { endTime = event.endTime || event.outTime || ""; } var start = timeToDecimal(startTime); var end = timeToDecimal(endTime); var top = start * 60; var height = (end - start) * 60; var eventCount = getDayEvents(day).length; var segmentWidth = 100 / Math.max(1, eventCount); return (jsx(OverlayTrigger, { placement: "top", delay: { show: 250, hide: 400 }, overlay: function (props) { return (jsx(Tooltip, __assign({}, props, { children: isMultiDay ? "".concat(event.title || event.description, ": ").concat(format(parseISO(event.date), "MMM d"), " - ").concat(format(parseISO(event.endDate || ""), "MMM d")) : "".concat(event.title || event.description, ": ").concat(event.startTime || event.inTime || "", " - ").concat(event.endTime || event.outTime || "") }))); }, children: jsx("div", { className: "position-absolute cursor-pointer rounded ".concat(event.badgeColor || event.dayColor), style: { backgroundColor: getDayColor(format(day, "yyyy-MM-dd")), top: "".concat(top, "px"), height: "".concat(height, "px"), left: "".concat(index * segmentWidth, "%"), width: "".concat(segmentWidth - 2, "%"), boxSizing: "border-box", zIndex: isMultiDay ? 1 : 0, }, onClick: function () { return handleEventClick(event); } }) }, "".concat(event.id, "-").concat(dayIndex))); }) }) }, dayIndex)); })] })] }) })), viewMode === "day" && (jsxs("div", { className: "schedule-view border border-gray-400 rounded p-1", children: [jsxs("div", { className: "row g-0 border-bottom border-gray-400 bg-light", children: [jsx("div", { className: "col-2 fw-semibold py-2 border-end border-gray-400 text-center", children: "Time" }), jsx("div", { className: "col-10 fw-semibold py-2 text-center", children: format(currentDate, "EEEE, MMMM d, yyyy") })] }), jsxs("div", { className: "row g-0", children: [jsx("div", { className: "col-2 border-end border-gray-400 bg-light", children: timeSlots.map(function (time, i) { return (jsx("div", { className: "text-end small py-2 pe-2 border-bottom border-gray-400 text-center h-60px", children: time }, i)); }) }), jsx("div", { className: "col-10 position-relative", style: { height: timeSlots.length * 60, overflow: "hidden" }, children: jsxs("div", { className: "col-10 position-relative", style: { height: timeSlots.length * 60, overflow: "hidden", width: "100%", }, children: [" ", getDayEvents(currentDate).length === 0 ? (jsx("img", { alt: "No Notifications", src: "media/logos/NoEventFound.png", className: "w-100 theme-light-show" })) : (jsx("div", { className: "position-absolute start-0 top-0 w-100 h-100 px-2", children: getDayEvents(currentDate).map(function (event, index) { // Determine if this is a multi-day event var isMultiDay = event.endDate && !isSameDay(parseISO(event.date), parseISO(event.endDate)); // Check if this is the first, middle, or last day of a multi-day event var isFirstDay = isMultiDay && isSameDay(currentDate, parseISO(event.date)); var isLastDay = isMultiDay && isSameDay(currentDate, parseISO(event.endDate || "")); // Calculate start/end times for this specific day var startTime = event.startTime || event.inTime || "00:00"; var endTime = event.endTime || event.outTime || "23:59"; // Adjust for first day of multi-day event if (isFirstDay && event.startTime) { startTime = event.startTime || event.inTime || ""; } // Adjust for last day of multi-day event if (isLastDay && event.endTime) { endTime = event.endTime || event.outTime || ""; } var start = timeToDecimal(startTime); var end = timeToDecimal(endTime); var top = start * 60; var height = (end - start) * 60; // Calculate dynamic width and position var eventCount = getDayEvents(currentDate).length; var segmentWidth = 100 / Math.max(1, eventCount); var left = index * segmentWidth; // 1% gap between events return (jsx(OverlayTrigger, { placement: "top", delay: { show: 250, hide: 400 }, overlay: function (props) { return (jsx(Tooltip, __assign({}, props, { children: isMultiDay ? "".concat(event.title || event.description, "\n").concat(format(parseISO(event.date), "MMM d"), " - ").concat(format(parseISO(event.endDate || ""), "MMM d")) : "".concat(event.title || event.description, "\n").concat(startTime, " - ").concat(endTime) }))); }, children: jsx("div", { className: "position-absolute cursor-pointer rounded ".concat(event.badgeColor || event.dayColor), style: { backgroundColor: getDayColor(event.date), top: "".concat(top, "px"), height: "".concat(height, "px"), left: "".concat(left + 0.5, "%"), width: "".concat(segmentWidth - 1, "%"), boxSizing: "border-box", zIndex: isMultiDay ? 1 : 0, }, onClick: function () { return handleEventClick(event); } }) }, event.id || index)); }) }))] }) })] })] }))] })); }; export { Calendar }; //# sourceMappingURL=index.esm.js.map