UNPKG

ftr-timetable

Version:

A versatile timetable component for React

850 lines (808 loc) 23.5 kB
import { jsx as r, jsxs as b, Fragment as q } from "react/jsx-runtime"; import $, { createContext as A, createRef as G, useContext as J, useMemo as x, useEffect as T, useState as K, useCallback as Q } from "react"; import { format as _, subHours as U, addHours as k, startOfDay as C, differenceInMinutes as D, differenceInDays as W, subDays as X, addDays as Z } from "date-fns"; import h from "styled-components"; const L = A( void 0 ), O = ({ children: e, ...o }) => /* @__PURE__ */ r(L.Provider, { value: { ...o, ref: G() }, children: e }), u = () => { const e = J(L); if (e === void 0) throw new Error( "useTimeTableContext must be used within the context of TimeTableContext" ); return e; }, ee = h.div` position: absolute; box-shadow: -1px -1px 2px 0 rgba(0, 0, 0, 0.2); `, j = $.memo(function({ date: e }) { const { ref: o, displayStyle: s, showTimeMarker: l, startingHour: d, styles: a } = u(), n = x(() => { const i = _(new Date(e), "yyyy-MM-dd"), t = _(U(/* @__PURE__ */ new Date(), d), "yyyy-MM-dd"); if (i !== t) return -1; const c = k(C(new Date(e)), d), f = D(/* @__PURE__ */ new Date(), c); return Math.max(f, 0); }, [e, d]); return T(() => { var t, c; if (typeof document > "u" || !o.current) return; const i = Math.max(n - 180, 0); (c = (t = o.current).scrollTo) == null || c.call(t, { top: s === "vertical" ? i : 0, left: s === "horizontal" ? i : 0, behavior: "smooth" }); }, [n, s, o]), n < 1 || !l ? null : /* @__PURE__ */ r( ee, { style: { backgroundColor: a.timeMarkerColor || "#666", width: s === "horizontal" ? "2px" : "80%", height: s === "horizontal" ? "33.333333%" : "2px", left: s === "horizontal" ? `${n}px` : 0, top: s === "horizontal" ? 0 : `${n}px` } } ); }), te = h.div` position: absolute; width: 100%; font-size: 0.875rem; line-height: 1.25rem; padding: 0 0.5rem; `, ie = h.div` position: absolute; height: 100%; font-size: 0.875rem; line-height: 1.25rem; padding: 1px 0; `, F = h.div` position: relative; height: 100%; padding: 1px; `, P = h.div` display: flex; position: relative; height: 100%; color: ${(e) => e.$styles.itemTextColor || "inherit"}; background: ${(e) => e.$styles.itemBackgroundColor || "#304151"}; cursor: pointer; border-radius: 5px; box-shadow: 0 2px 5px -4px rgba(0, 0, 0, 0.4); &:hover { box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.1); background: ${(e) => e.$styles.itemHoverBackgroundColor || e.$styles.itemBackgroundColor || "#374151"}; } &.ftr-timetable-item__vertical { padding: 0.25rem; flex-direction: column; .ftr-timetable-item__inner { top: 62px; } } &.ftr-timetable-item__horizontal { padding: 0.125rem 0.25rem; flex-direction: row; .ftr-timetable-item__inner { left: 164px; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } } &.item-cancelled { opacity: 0.5; .ftr-timetable-item__inner { text-decoration: line-through; } } .ftr-timetable-item__inner { position: sticky; overflow: hidden; text-overflow: ellipsis; font-size: 12px; .ftr-timetable-item__info { font-size: 10px; font-weight: normal; opacity: 0.9; } } `, re = $.memo(function({ item: e, eventStartOffset: o, eventSize: s, intersections: l, offset: d }) { const { onItemClick: a, renderItem: n, styles: i } = u(); return /* @__PURE__ */ r( te, { title: e.name, style: { top: `${o}px`, height: `${s}px`, left: `calc(100% / ${l + 1} * ${d})`, maxWidth: `calc(100% / ${l + 1})` }, children: /* @__PURE__ */ r(F, { onClick: () => a == null ? void 0 : a(e), children: n ? n(e) : /* @__PURE__ */ r( P, { $styles: i, className: "ftr-timetable-item ftr-timetable-item__vertical", style: e.style, children: /* @__PURE__ */ b("div", { className: "ftr-timetable-item__inner", children: [ /* @__PURE__ */ r("div", { children: e.name }), e.info && /* @__PURE__ */ r("div", { className: "ftr-timetable-item__info", children: e.info }) ] }) } ) }) } ); }), oe = $.memo(function({ item: e, eventStartOffset: o, eventSize: s, intersections: l, offset: d }) { const { onItemClick: a, renderItem: n, styles: i } = u(); return /* @__PURE__ */ r( ie, { title: e.name, style: { left: `${o}px`, width: `${s}px`, top: `calc(100% / ${l + 1} * ${d})`, maxHeight: `calc(100% / ${l + 1})` }, children: /* @__PURE__ */ r(F, { onClick: () => a == null ? void 0 : a(e), children: n ? n(e) : /* @__PURE__ */ r( P, { $styles: i, className: `ftr-timetable-item ftr-timetable-item__horizontal${e.cancelled ? " item-cancelled" : ""}${e.className ? ` ${e.className}` : ""}`, style: e.style, children: /* @__PURE__ */ b("div", { className: "ftr-timetable-item__inner", children: [ /* @__PURE__ */ r("div", { children: e.name }), e.info && /* @__PURE__ */ r("div", { className: "ftr-timetable-item__info", children: e.info }) ] }) } ) }) } ); }), V = $.memo(function({ item: e, intersections: o, offset: s }) { const { startingHour: l, numberOfHours: d, selectedDate: a, displayStyle: n } = u(), i = W( new Date(e.startDate), new Date(a) ), t = X( k(C(new Date(e.startDate)), l), i ), c = new Date( Math.max(new Date(e.startDate).getTime(), new Date(t).getTime()) ), m = D(c, t), f = D( new Date(e.endDate), c ), y = Math.min( f, d * 60 - m ); return n === "vertical" ? /* @__PURE__ */ r( re, { item: e, intersections: o, offset: s, eventStartOffset: m, eventSize: y } ) : /* @__PURE__ */ r( oe, { item: e, intersections: o, offset: s, eventStartOffset: m, eventSize: y } ); }), E = (e) => x(() => e.reduce((l, d, a) => { const n = new Date(d.startDate).getTime(), i = new Date(d.endDate).getTime(); let t = 0; const c = l.filter( (m) => n >= m.start && n < m.end || i >= m.start && i < m.end ); for (const m of c) { if (m.offset > 0) { t--; break; } if (m.intersections === 0) { m.intersections += 1; continue; } for (const f of l.filter( (y) => m.with.includes(y.index) )) (n >= f.start && n < f.end || i >= f.start && i < f.end) && (m.intersections += 1); } return [ ...l, { item: d, start: n, end: i, offset: c.length + t, intersections: c.length, index: a, with: c.map((m) => m.index) } ]; }, []), [e]), ne = h.div` &::-webkit-scrollbar { width: 0px; height: 8px; background: #1f2937; } &::-webkit-scrollbar-thumb { background: #555; border-radius: 50%; border: solid 2px #1f2937; } &::-webkit-scrollbar-thumb:hover { background: hsl(from #888 h s calc(l - 5)); } position: relative; overflow: auto; width: 100%; max-height: 100%; max-width: 100vw; box-sizing: border-box !important; * { box-sizing: border-box !important; } `, le = h.div` position: absolute; width: 100%; height: 100%; left: 0; top: 0; color: ${(e) => e.$styles.dateTextColor || "inherit"}; background: ${(e) => e.$styles.backgroundColor || "#1f2937"}; .ftr-timetable-datetime { display: flex; flex-direction: row; position: sticky; top: 0; z-index: 3; background: ${(e) => e.$styles.dateBackgroundColor || "#1f2937"}; width: 100%; height: 44px; &__date { position: sticky; top: 0; left: 0; z-index: 2; background: ${(e) => e.$styles.datePickerBackgroundColor || e.$styles.dateBackgroundColor || "#1f2937"}; display: flex; justify-content: space-between; width: 10rem; height: 100%; flex-shrink: 0; flex-direction: row; } &__select { display: flex; flex-direction: row; align-items: center; width: 100%; height: 100%; padding-left: 0.5rem; border-right: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; border-bottom: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; select { background: transparent url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='${(e) => { var o; return ((o = e.$styles.dateTextColor) == null ? void 0 : o.replace("#", "%23")) || "%23fff"; }}'><polygon points='0,0 100,0 50,50'/></svg>") no-repeat calc(100% - 10px) calc(50% + 3px); background-size: 10px; color: inherit; font-family: inherit; border: none; outline: none; font-size: 0.875rem; line-height: 1.25rem; width: 100%; -webkit-appearance: none; appearance: none; } } &__hours { display: flex; flex-direction: row; position: relative; border-bottom: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; width: 100%; height: 100%; } &__hour { display: flex; flex-direction: column; justify-content: flex-end; width: 60px; padding-left: 0.25rem; padding-bottom: 0.25rem; font-size: 0.75rem; height: 100%; &:not(:first-child) { border-left: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; } } } `, ae = h.div` display: flex; flex-direction: row; // background-color: ${(e) => e.$styles.backgroundColor || "#1f2937"}; height: 60px; .ftr-timetable-location { height: 100%; width: 10rem; z-index: 2; position: sticky; top: 0; left: 0; color: ${(e) => e.$styles.locationTextColor || "inherit"}; background: ${(e) => e.$styles.locationBackgroundColor || "#000"}; &__inner { display: flex; align-items: center; height: 100%; padding: 0 0.5rem; font-size: 0.875rem; line-height: 1.25rem; border-right: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; border-bottom: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; } &__name { width: 100%; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } } .ftr-timetable-location-items { flex: 1; position: relative; height: 100%; border-bottom: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; // background-color: ${(e) => e.$styles.backgroundColor || "#1f2937"}; } `, se = $.memo(function({ location: e }) { const { items: o, onLocationClick: s, renderLocation: l, styles: d } = u(), a = o.filter( (i) => i.locationId === e.id ), n = E(a); return /* @__PURE__ */ b(ae, { $styles: d, children: [ /* @__PURE__ */ r( "div", { className: "ftr-timetable-location ftr-timetable-location__horizontal", "data-testid": `timetable-location-${e.id}`, title: e.name, onClick: () => s == null ? void 0 : s(e), style: e.style, children: l ? l(e) : /* @__PURE__ */ r("div", { className: "ftr-timetable-location__inner", children: /* @__PURE__ */ r("div", { className: "ftr-timetable-location__name", children: e.name }) }) } ), /* @__PURE__ */ r("div", { className: "ftr-timetable-location-items", children: n.map((i, t) => /* @__PURE__ */ r( V, { item: i.item, intersections: i.intersections, offset: i.offset }, `tt_${i.item.id}_${t}` )) }) ] }); }), de = ({ dateChange: e, locations: o, dates: s, hours: l, selectedDate: d }) => { const { ref: a, dateFormat: n, styles: i } = u(); return /* @__PURE__ */ r( ne, { ref: a, "data-testid": "timetable-horizontal", className: "ftr-timetable ftr-timetable__horizontal", style: { height: `${o.length * 60 + 52}px` }, children: /* @__PURE__ */ b( le, { $styles: i, style: { minWidth: `${l.length * 60 + 160}px` }, children: [ /* @__PURE__ */ b("div", { className: "ftr-timetable-datetime", children: [ /* @__PURE__ */ r("div", { className: "ftr-timetable-datetime__date", children: /* @__PURE__ */ r("div", { className: "ftr-timetable-datetime__select", children: /* @__PURE__ */ r( "select", { value: d, onChange: (t) => e(t.target.value), children: s.map((t) => /* @__PURE__ */ r("option", { value: t, children: _(new Date(t), n) }, t)) } ) }) }), /* @__PURE__ */ b("div", { className: "ftr-timetable-datetime__hours", children: [ l.map((t, c) => /* @__PURE__ */ r("div", { className: "ftr-timetable-datetime__hour", children: t.display }, `hour_${c}`)), /* @__PURE__ */ r(j, { date: d }) ] }) ] }), o.map((t, c) => /* @__PURE__ */ r(se, { location: t }, `location_${c}`)) ] } ) } ); }, ce = h.div` &::-webkit-scrollbar { width: 0px; height: 8px; background: #1f2937; } &::-webkit-scrollbar-thumb { background: #555; border-radius: 50%; border: solid 2px #1f2937; } &::-webkit-scrollbar-thumb:hover { background: hsl(from #888 h s calc(l - 5)); } height: 100%; max-width: 100vw; position: relative; overflow: auto; box-sizing: border-box !important; * { box-sizing: border-box !important; } `, me = h.div` position: absolute; width: 100%; height: 100%; left: 0; top: 0; transform: translateY(-1px); color: ${(e) => e.$styles.textColor || "#fff"}; .ftr-timetable-datetime { display: flex; flex-direction: row; width: 100%; &__container { width: 8rem; color: ${(e) => e.$styles.dateTextColor || "inherit"}; background: ${(e) => e.$styles.dateBackgroundColor || "#1f2937"}; position: sticky; left: 0; top: 0; z-index: 3; } &__date { width: 100%; height: 60px; background: ${(e) => e.$styles.datePickerBackgroundColor || e.$styles.dateBackgroundColor || "#1f2937"}; position: sticky; top: 0; z-index: 2; } &__select { display: flex; flex-direction: row; align-items: center; width: 100%; height: 100%; padding-left: 0.5rem; border-right: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; border-bottom: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; select { width: 100%; background: transparent url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='${(e) => { var o; return ((o = e.$styles.dateTextColor) == null ? void 0 : o.replace("#", "%23")) || "%23fff"; }}'><polygon points='0,0 100,0 50,50'/></svg>") no-repeat calc(100% - 10px) calc(50% + 3px); background-size: 10px; color: inherit; font-family: inherit; border: none; outline: none; font-size: 0.875rem; line-height: 1.25rem; -webkit-appearance: none; appearance: none; } } &__hours { position: relative; z-index: 1; border-right: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; } &__hour { display: flex; justify-content: flex-end; height: 60px; padding-top: 0.25rem; padding-right: 0.5rem; position: relative; z-index: 2; font-size: 0.75rem; line-height: 1rem; &:not(:first-child) { border-top: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; } } } .ftr-timetable-locations { display: flex; flex-direction: row; flex: 1; position: relative; } `, fe = h.div` display: flex; flex: 1; flex-direction: column; .ftr-timetable-location { height: 60px; position: sticky; top: 0; z-index: 2; color: ${(e) => e.$styles.locationTextColor || "inherit"}; background: ${(e) => e.$styles.locationBackgroundColor || "#000"}; &__inner { display: flex; flex-direction: row; align-items: center; padding: 0 0.5rem; font-size: 0.875rem; line-height: 1.25rem; height: 100%; border-right: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; border-bottom: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; } &__name { width: 100%; text-overflow: ellipsis; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; } } .ftr-timetable-location-items { display: flex; flex: 1; flex-direction: column; position: relative; background: ${(e) => e.$styles.backgroundColor || "#1f2937"}; border-right: ${(e) => e.$styles.borderStyle || "solid 2px #374151"}; } `, he = $.memo(function({ location: e }) { const { items: o, onLocationClick: s, renderLocation: l, styles: d } = u(), a = o.filter( (i) => i.locationId === e.id ), n = E(a); return /* @__PURE__ */ b(fe, { $styles: d, children: [ /* @__PURE__ */ r( "div", { className: "ftr-timetable-location ftr-timetable-location__vertical", "data-testid": `timetable-location-${e.id}`, title: e.name, onClick: () => s == null ? void 0 : s(e), style: e.style, children: l ? l(e) : /* @__PURE__ */ r("div", { className: "ftr-timetable-location__inner", children: /* @__PURE__ */ r("div", { className: "ftr-timetable-location__name", children: e.name }) }) } ), /* @__PURE__ */ r("div", { className: "ftr-timetable-location-items", children: n.map((i, t) => /* @__PURE__ */ r( V, { item: i.item, intersections: i.intersections, offset: i.offset }, `tt_${i.item.id}_${t}` )) }) ] }); }), be = ({ dateChange: e, locations: o, dates: s, hours: l, selectedDate: d }) => { const { ref: a, dateFormat: n, styles: i } = u(); return /* @__PURE__ */ r( ce, { ref: a, "data-testid": "timetable-vertical", className: "ftr-timetable ftr-timetable-vertical", children: /* @__PURE__ */ r(me, { $styles: i, children: /* @__PURE__ */ b( "div", { className: "ftr-timetable-datetime", style: { minWidth: `${o.length * 200}px` }, children: [ /* @__PURE__ */ b("div", { className: "ftr-timetable-datetime__container", children: [ /* @__PURE__ */ r("div", { className: "ftr-timetable-datetime__date", children: /* @__PURE__ */ r("div", { className: "ftr-timetable-datetime__select", children: /* @__PURE__ */ r( "select", { value: d, onChange: (t) => e(t.target.value), children: s.map((t) => /* @__PURE__ */ r("option", { value: t, children: _(new Date(t), n) }, t)) } ) }) }), /* @__PURE__ */ b("div", { className: "ftr-timetable-datetime__hours", children: [ l.map((t, c) => /* @__PURE__ */ r( "div", { className: "ftr-timetable-datetime__hour", children: t.display }, `hour_${c}` )), /* @__PURE__ */ r(j, { date: d }) ] }) ] }), /* @__PURE__ */ r("div", { className: "ftr-timetable-locations", children: o.map((t, c) => /* @__PURE__ */ r(he, { location: t }, `location_${c}`)) }) ] } ) }) } ); }, pe = (e, o, s, l) => x(() => { const a = []; if (!(e != null && e.length) || !l) return a; const n = k(C(new Date(l)), o), i = k(n, s); for (const t of e) (new Date(t.startDate) >= n && new Date(t.startDate) < i || new Date(t.endDate) > n && new Date(t.endDate) <= i) && a.push(t); return a.sort( (t, c) => new Date(t.startDate).getTime() - new Date(c.startDate).getTime() ); }, [l, o, s, e]), xe = (e, o) => x(() => { if (o) return o; const l = e.flatMap((t) => { const c = []; return t.startDate && c.push(new Date(t.startDate).getTime()), t.endDate && c.push(new Date(t.endDate).getTime()), c; }).sort(); if (!l.length) return []; const d = l[0], a = l[l.length - 1], n = W(a, d), i = []; for (let t = 0; t <= n; t++) { const c = _(Z(new Date(d), t), "yyyy-MM-dd"); i.push(c); } return i; }, [e, o]), ue = (e, o) => x(() => { var d; let l = []; for (const a of e) if ((d = a.items) != null && d.length) for (const n of a.items.filter((i) => { const t = new Date(i.startDate), c = new Date(i.endDate); return isNaN(t.getTime()) || isNaN(c.getTime()) ? (console.error("Invalid startDate or endDate format", i), !1) : !0; })) l.push({ ...n, locationId: a.id }); return o != null && o.length && (l = l.concat( o.filter((a) => { const n = new Date(a.startDate), i = new Date(a.endDate); return isNaN(n.getTime()) || isNaN(i.getTime()) ? (console.error("Invalid startDate or endDate format", a), !1) : !0; }) )), l; }, [o, e]), _e = ({ locations: e, items: o, dates: s, dateFormat: l = "eee dd MMMM", onItemClick: d, onLocationClick: a, onDateChange: n, variant: i, renderItem: t, renderLocation: c, startingHour: m = 6, numberOfHours: f = 24, showTimeMarker: y = !0, styles: z }) => { const H = x(() => ({ backgroundColor: "#1f2937", dateBackgroundColor: "#1f2937", textColor: "#fff", borderStyle: "solid 2px #374151", ...z }), [z]), [p, N] = K(), I = ue(e, o), Y = pe( I, m, f, p ), v = xe(I, s), S = x(() => i || (e.length > 1 ? "horizontal" : "vertical"), [i, e]), M = x(() => { const g = []; for (let w = m; w < m + f; w++) { const R = w < 24 ? w : w - 24; g.push({ hour: w, display: `${R < 10 ? "0" : ""}${R}:00` }); } return g; }, [f, m]); T(() => { N((g) => g || v[0]); }, [v]), T(() => { p && (n == null || n(p)); }, [n, p]); const B = Q((g) => { N(g); }, []); return p ? /* @__PURE__ */ r( O, { startingHour: m, numberOfHours: f, onItemClick: d, onLocationClick: a, displayStyle: S, renderItem: t, renderLocation: c, dateFormat: l, selectedDate: p, showTimeMarker: y, items: Y, styles: H, children: S === "horizontal" ? /* @__PURE__ */ r( de, { dateChange: B, hours: M, locations: e, dates: v, selectedDate: p } ) : /* @__PURE__ */ r( be, { dateChange: B, hours: M, locations: e, dates: v, selectedDate: p } ) } ) : /* @__PURE__ */ r(q, {}); }; export { _e as TimeTable }; //# sourceMappingURL=index.es.js.map