* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main: - Rebrand Cal.com to Cal.diy across the entire codebase - Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions - Switch license from AGPL-3.0 to MIT - Remove docs/ directory (migrated to Nextra at cal.diy) - Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc. - Clean up .env.example for self-hosted Cal.diy - Update Docker image references to calcom/cal.diy - Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork - Add PR welcome bot for Cal.diy contributors - Fix API v2 breaking changes oasdiff ignore entries - Replace Blacksmith CI runners with default GitHub Actions 3893 files changed, 20789 insertions(+), 411020 deletions(-) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702) Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * rip out org related comments in api v2 --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
216 lines
9.1 KiB
TypeScript
216 lines
9.1 KiB
TypeScript
import {
|
|
CalendarStoreContext,
|
|
createCalendarStore,
|
|
useCalendarStore,
|
|
} from "@calcom/features/calendars/weeklyview/state/store";
|
|
import classNames from "@calcom/ui/classNames";
|
|
import type React from "react";
|
|
import { useEffect, useMemo, useRef } from "react";
|
|
import "@calcom/features/calendars/weeklyview/styles/styles.css";
|
|
import type { CalendarComponentProps } from "@calcom/features/calendars/weeklyview/types/state";
|
|
import { getDaysBetweenDates, getHoursToDisplay } from "@calcom/features/calendars/weeklyview/utils";
|
|
import { CurrentTime } from "./currentTime";
|
|
import { DateValues } from "./DateValues";
|
|
import { AvailableCellsForDay, EmptyCell } from "./event/Empty";
|
|
import { EventList } from "./event/EventList";
|
|
import { SchedulerColumns } from "./grid";
|
|
import { SchedulerHeading } from "./heading/SchedulerHeading";
|
|
import { HorizontalLines } from "./horizontalLines";
|
|
import { Spinner } from "./spinner/Spinner";
|
|
import { VerticalLines } from "./verticalLines";
|
|
|
|
function CalendarInner(props: CalendarComponentProps) {
|
|
const container = useRef<HTMLDivElement | null>(null);
|
|
const containerNav = useRef<HTMLDivElement | null>(null);
|
|
const containerOffset = useRef<HTMLDivElement | null>(null);
|
|
const schedulerGrid = useRef<HTMLOListElement | null>(null);
|
|
const initialState = useCalendarStore((state) => state.initState);
|
|
|
|
const startDate = useCalendarStore((state) => state.startDate);
|
|
const endDate = useCalendarStore((state) => state.endDate);
|
|
const startHour = useCalendarStore((state) => state.startHour || 0);
|
|
const endHour = useCalendarStore((state) => state.endHour || 23);
|
|
const usersCellsStopsPerHour = useCalendarStore((state) => state.gridCellsPerHour || 4);
|
|
const availableTimeslots = useCalendarStore((state) => state.availableTimeslots);
|
|
const hideHeader = useCalendarStore((state) => state.hideHeader);
|
|
const timezone = useCalendarStore((state) => state.timezone);
|
|
const showBackgroundPattern = useCalendarStore((state) => state.showBackgroundPattern);
|
|
const showBorder = useCalendarStore((state) => state.showBorder ?? true);
|
|
const borderColor = useCalendarStore((state) => state.borderColor ?? "default");
|
|
const scrollToCurrentTime = useCalendarStore((state) => state.scrollToCurrentTime ?? true);
|
|
const updateCurrentTimeOnFocus = useCalendarStore((state) => state.updateCurrentTimeOnFocus ?? false);
|
|
|
|
const days = useMemo(() => getDaysBetweenDates(startDate, endDate), [startDate, endDate]);
|
|
|
|
const hours = useMemo(
|
|
() => getHoursToDisplay(startHour || 0, endHour || 23, timezone),
|
|
[startHour, endHour, timezone]
|
|
);
|
|
const numberOfGridStopsPerDay = hours.length * usersCellsStopsPerHour;
|
|
const hourSize = 58;
|
|
|
|
// Initalise State on initial mount and when props change
|
|
useEffect(() => {
|
|
initialState(props);
|
|
}, [props, initialState]);
|
|
|
|
return (
|
|
<MobileNotSupported>
|
|
<div
|
|
className={classNames("scheduler-wrapper flex h-full w-full flex-col")}
|
|
style={
|
|
{
|
|
"--one-minute-height": `calc(${hourSize}px/60)`,
|
|
"--gridDefaultSize": `${hourSize}px`,
|
|
} as React.CSSProperties // This can't live in the css file because it's a dynamic value and css variable gets super
|
|
}>
|
|
{hideHeader !== true && <SchedulerHeading />}
|
|
{props.isPending && <Spinner />}
|
|
<div
|
|
ref={container}
|
|
className="bg-default dark:bg-cal-muted relative isolate flex h-full flex-auto flex-col">
|
|
<div
|
|
style={{ width: "165%" }}
|
|
className="flex max-w-full flex-none flex-col sm:max-w-none md:max-w-full">
|
|
<DateValues
|
|
containerNavRef={containerNav}
|
|
days={days}
|
|
showBorder={showBorder}
|
|
borderColor={borderColor}
|
|
/>
|
|
<div className="relative flex flex-auto">
|
|
<CurrentTime
|
|
timezone={timezone}
|
|
scrollToCurrentTime={scrollToCurrentTime}
|
|
updateOnFocus={updateCurrentTimeOnFocus}
|
|
/>
|
|
<div
|
|
className={classNames(
|
|
"bg-default dark:bg-cal-muted ring-muted sticky left-0 z-10 w-16 flex-none ring-1",
|
|
showBorder &&
|
|
(borderColor === "subtle"
|
|
? "border-subtle border-l border-r"
|
|
: "border-default border-l border-r")
|
|
)}
|
|
/>
|
|
<div
|
|
className="grid flex-auto grid-cols-1 grid-rows-1 [--disabled-gradient-background:#F8F9FB] [--disabled-gradient-foreground:#E6E7EB] dark:[--disabled-gradient-background:#262626] dark:[--disabled-gradient-foreground:#393939]"
|
|
style={
|
|
showBackgroundPattern === false
|
|
? undefined
|
|
: {
|
|
backgroundColor: "var(--disabled-gradient-background)",
|
|
background:
|
|
"repeating-linear-gradient(-45deg, var(--disabled-gradient-background), var(--disabled-gradient-background) 2.5px, var(--disabled-gradient-foreground) 2.5px, var(--disabled-gradient-foreground) 5px)",
|
|
}
|
|
}>
|
|
<HorizontalLines
|
|
hours={hours}
|
|
numberOfGridStopsPerCell={usersCellsStopsPerHour}
|
|
containerOffsetRef={containerOffset}
|
|
borderColor={borderColor}
|
|
/>
|
|
<VerticalLines days={days} borderColor={borderColor} />
|
|
|
|
<SchedulerColumns
|
|
offsetHeight={containerOffset.current?.offsetHeight}
|
|
gridStopsPerDay={numberOfGridStopsPerDay}>
|
|
{/*Loop over events per day */}
|
|
{days.map((day, i) => {
|
|
return (
|
|
<li key={day.toISOString()} className="relative" style={{ gridColumnStart: i + 1 }}>
|
|
<EventList day={day} />
|
|
{/* <BlockedList day={day} containerRef={container} /> */}
|
|
</li>
|
|
);
|
|
})}
|
|
</SchedulerColumns>
|
|
|
|
{/* Empty Cells */}
|
|
<SchedulerColumns
|
|
ref={schedulerGrid}
|
|
offsetHeight={containerOffset.current?.offsetHeight}
|
|
gridStopsPerDay={numberOfGridStopsPerDay}>
|
|
<>
|
|
{[...Array(days.length)].map((_, i) => (
|
|
<li
|
|
className="relative"
|
|
key={i}
|
|
style={{
|
|
gridColumnStart: i + 1,
|
|
gridRow: `1 / span ${numberOfGridStopsPerDay}`,
|
|
}}>
|
|
{/* While startDate < endDate: */}
|
|
{availableTimeslots ? (
|
|
<AvailableCellsForDay
|
|
key={days[i].toISOString()}
|
|
timezone={timezone}
|
|
day={days[i]}
|
|
startHour={startHour}
|
|
availableSlots={availableTimeslots}
|
|
/>
|
|
) : (
|
|
<>
|
|
{[...Array(numberOfGridStopsPerDay)].map((_, j) => {
|
|
const key = `${i}-${j}`;
|
|
return (
|
|
<EmptyCell
|
|
key={key}
|
|
day={days[i]}
|
|
gridCellIdx={j}
|
|
totalGridCells={numberOfGridStopsPerDay}
|
|
selectionLength={endHour - startHour}
|
|
startHour={startHour}
|
|
timezone={timezone}
|
|
/>
|
|
);
|
|
})}
|
|
</>
|
|
)}
|
|
</li>
|
|
))}
|
|
</>
|
|
</SchedulerColumns>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</MobileNotSupported>
|
|
);
|
|
}
|
|
|
|
export function Calendar(props: CalendarComponentProps) {
|
|
const storeRef = useRef<ReturnType<typeof createCalendarStore> | null>(null);
|
|
|
|
if (!storeRef.current) {
|
|
storeRef.current = createCalendarStore();
|
|
storeRef.current.getState().initState(props);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (storeRef.current) {
|
|
storeRef.current.getState().initState(props);
|
|
}
|
|
}, [props]);
|
|
|
|
return (
|
|
<CalendarStoreContext.Provider value={storeRef.current}>
|
|
<CalendarInner {...props} />
|
|
</CalendarStoreContext.Provider>
|
|
);
|
|
}
|
|
|
|
/** @todo Will be removed once we have mobile support */
|
|
const MobileNotSupported = ({ children }: { children: React.ReactNode }) => {
|
|
return (
|
|
<>
|
|
<div className="flex h-full flex-col items-center justify-center sm:hidden">
|
|
<h1 className="text-2xl font-bold">Mobile not supported yet </h1>
|
|
<p className="text-subtle">Please use a desktop browser to view this page</p>
|
|
</div>
|
|
<div className="hidden h-full sm:block">{children}</div>
|
|
</>
|
|
);
|
|
};
|