ftr-timetable
Version:
A versatile timetable component for React
850 lines (808 loc) • 23.5 kB
JavaScript
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