calendarkit-pro 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/index.d.mts +161 -0
- package/dist/index.d.ts +161 -0
- package/dist/index.js +2844 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2822 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +89 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2844 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var React15 = require('react');
|
|
4
|
+
var core = require('@dnd-kit/core');
|
|
5
|
+
var modifiers = require('@dnd-kit/modifiers');
|
|
6
|
+
var framerMotion = require('framer-motion');
|
|
7
|
+
var dateFns = require('date-fns');
|
|
8
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
9
|
+
var clsx = require('clsx');
|
|
10
|
+
var tailwindMerge = require('tailwind-merge');
|
|
11
|
+
var lucideReact = require('lucide-react');
|
|
12
|
+
var dateFnsTz = require('date-fns-tz');
|
|
13
|
+
var utilities = require('@dnd-kit/utilities');
|
|
14
|
+
var rrule = require('rrule');
|
|
15
|
+
|
|
16
|
+
function _interopNamespace(e) {
|
|
17
|
+
if (e && e.__esModule) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var React15__namespace = /*#__PURE__*/_interopNamespace(React15);
|
|
35
|
+
|
|
36
|
+
function cn(...inputs) {
|
|
37
|
+
return tailwindMerge.twMerge(clsx.clsx(inputs));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/components/ui/button.tsx
|
|
41
|
+
var buttonVariants = classVarianceAuthority.cva(
|
|
42
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
43
|
+
{
|
|
44
|
+
variants: {
|
|
45
|
+
variant: {
|
|
46
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
47
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
48
|
+
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
49
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
50
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
51
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
52
|
+
},
|
|
53
|
+
size: {
|
|
54
|
+
default: "h-10 px-4 py-2",
|
|
55
|
+
sm: "h-9 rounded-md px-3",
|
|
56
|
+
lg: "h-11 rounded-md px-8",
|
|
57
|
+
icon: "h-10 w-10"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
defaultVariants: {
|
|
61
|
+
variant: "default",
|
|
62
|
+
size: "default"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
);
|
|
66
|
+
var Button = React15__namespace.forwardRef(
|
|
67
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
68
|
+
const Comp = "button";
|
|
69
|
+
return /* @__PURE__ */ React15__namespace.createElement(
|
|
70
|
+
Comp,
|
|
71
|
+
{
|
|
72
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
73
|
+
ref,
|
|
74
|
+
...props
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
Button.displayName = "Button";
|
|
80
|
+
var CalendarHeader = ({
|
|
81
|
+
currentDate,
|
|
82
|
+
onPrev,
|
|
83
|
+
onNext,
|
|
84
|
+
onToday,
|
|
85
|
+
view,
|
|
86
|
+
onViewChange,
|
|
87
|
+
onMenuClick,
|
|
88
|
+
isDarkMode,
|
|
89
|
+
onThemeToggle,
|
|
90
|
+
translations,
|
|
91
|
+
hideViewSwitcher,
|
|
92
|
+
language,
|
|
93
|
+
onLanguageChange,
|
|
94
|
+
locale
|
|
95
|
+
}) => {
|
|
96
|
+
const viewConfig = [
|
|
97
|
+
{ key: "month", icon: lucideReact.CalendarDays },
|
|
98
|
+
{ key: "week", icon: lucideReact.CalendarRange },
|
|
99
|
+
{ key: "day", icon: lucideReact.Calendar },
|
|
100
|
+
{ key: "agenda", icon: lucideReact.ListTodo }
|
|
101
|
+
];
|
|
102
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col md:flex-row items-center justify-between px-3 md:px-5 py-3 border-b-[0px] border-border/50 bg-gradient-to-r from-background via-background to-muted/20 gap-3 md:gap-0 min-h-[64px]" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 w-full md:w-auto justify-between md:justify-start" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 md:gap-3" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
103
|
+
Button,
|
|
104
|
+
{
|
|
105
|
+
variant: "ghost",
|
|
106
|
+
size: "icon",
|
|
107
|
+
className: "text-muted-foreground hover:text-foreground hover:bg-accent/80 rounded-xl h-10 w-10 hidden md:inline-flex transition-all duration-200",
|
|
108
|
+
onClick: onMenuClick
|
|
109
|
+
},
|
|
110
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Menu, { className: "h-5 w-5" })
|
|
111
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
112
|
+
Button,
|
|
113
|
+
{
|
|
114
|
+
variant: "outline",
|
|
115
|
+
onClick: onToday,
|
|
116
|
+
className: "h-9 px-5 rounded-xl text-sm font-medium hidden sm:inline-flex border-[0.5px] border-border/60 hover:border-primary/40 hover:bg-primary/5 hover:text-primary transition-all duration-200"
|
|
117
|
+
},
|
|
118
|
+
translations.today
|
|
119
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center bg-muted/40 rounded-xl p-0.5" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
120
|
+
Button,
|
|
121
|
+
{
|
|
122
|
+
variant: "ghost",
|
|
123
|
+
size: "icon",
|
|
124
|
+
onClick: onPrev,
|
|
125
|
+
className: "rounded-lg h-8 w-8 hover:bg-background/80 transition-all duration-200"
|
|
126
|
+
},
|
|
127
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronLeft, { className: "h-4 w-4 text-muted-foreground" })
|
|
128
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
129
|
+
Button,
|
|
130
|
+
{
|
|
131
|
+
variant: "ghost",
|
|
132
|
+
size: "icon",
|
|
133
|
+
onClick: onNext,
|
|
134
|
+
className: "rounded-lg h-8 w-8 hover:bg-background/80 transition-all duration-200"
|
|
135
|
+
},
|
|
136
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronRight, { className: "h-4 w-4 text-muted-foreground" })
|
|
137
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "ml-2 md:ml-4" }, /* @__PURE__ */ React15__namespace.default.createElement("h2", { className: "text-lg md:text-xl font-semibold text-foreground whitespace-nowrap capitalize tracking-tight" }, dateFns.format(currentDate, "MMMM yyyy", { locale }))))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 md:gap-3 w-full md:w-auto justify-end" }, onLanguageChange && language && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
138
|
+
Button,
|
|
139
|
+
{
|
|
140
|
+
variant: "ghost",
|
|
141
|
+
size: "sm",
|
|
142
|
+
onClick: () => onLanguageChange(language === "en" ? "fr" : "en"),
|
|
143
|
+
className: "h-9 px-3 rounded-xl text-xs font-semibold text-muted-foreground hover:text-foreground hover:bg-accent/80 transition-all duration-200 uppercase tracking-wider"
|
|
144
|
+
},
|
|
145
|
+
language
|
|
146
|
+
), onThemeToggle && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
147
|
+
Button,
|
|
148
|
+
{
|
|
149
|
+
variant: "ghost",
|
|
150
|
+
size: "icon",
|
|
151
|
+
className: "rounded-xl h-9 w-9 hover:bg-accent/80 transition-all duration-200",
|
|
152
|
+
onClick: onThemeToggle
|
|
153
|
+
},
|
|
154
|
+
isDarkMode ? /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Sun, { className: "h-4 w-4 text-amber-500" }) : /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Moon, { className: "h-4 w-4 text-muted-foreground" })
|
|
155
|
+
), !hideViewSwitcher && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center bg-muted/50 backdrop-blur-sm rounded-xl p-1" }, viewConfig.map(({ key, icon: Icon }) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
156
|
+
Button,
|
|
157
|
+
{
|
|
158
|
+
key,
|
|
159
|
+
variant: "ghost",
|
|
160
|
+
size: "sm",
|
|
161
|
+
onClick: () => onViewChange(key),
|
|
162
|
+
className: cn(
|
|
163
|
+
"h-8 px-3 text-xs rounded-lg transition-all duration-200 gap-1.5",
|
|
164
|
+
view === key ? "bg-background shadow-sm text-foreground font-medium border-[0.5px] border-border/50" : "text-muted-foreground hover:text-foreground hover:bg-background/50"
|
|
165
|
+
)
|
|
166
|
+
},
|
|
167
|
+
/* @__PURE__ */ React15__namespace.default.createElement(Icon, { className: "h-3.5 w-3.5" }),
|
|
168
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "hidden sm:inline" }, translations[key])
|
|
169
|
+
)))));
|
|
170
|
+
};
|
|
171
|
+
var getMonthGrid = (date, weekStartOn = 0) => {
|
|
172
|
+
const monthStart = dateFns.startOfMonth(date);
|
|
173
|
+
const monthEnd = dateFns.endOfMonth(monthStart);
|
|
174
|
+
const startDate = dateFns.startOfWeek(monthStart, { weekStartsOn: weekStartOn });
|
|
175
|
+
const endDate = dateFns.endOfWeek(monthEnd, { weekStartsOn: weekStartOn });
|
|
176
|
+
return dateFns.eachDayOfInterval({
|
|
177
|
+
start: startDate,
|
|
178
|
+
end: endDate
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
var MiniCalendar = ({
|
|
182
|
+
currentDate,
|
|
183
|
+
onDateChange,
|
|
184
|
+
onViewChange,
|
|
185
|
+
className
|
|
186
|
+
}) => {
|
|
187
|
+
const [viewDate, setViewDate] = React15__namespace.default.useState(currentDate);
|
|
188
|
+
React15__namespace.default.useEffect(() => {
|
|
189
|
+
setViewDate(currentDate);
|
|
190
|
+
}, [currentDate]);
|
|
191
|
+
const days = React15__namespace.default.useMemo(() => getMonthGrid(viewDate), [viewDate]);
|
|
192
|
+
const weekDays = ["S", "M", "T", "W", "T", "F", "S"];
|
|
193
|
+
const handlePrev = () => {
|
|
194
|
+
const newDate = dateFns.subMonths(viewDate, 1);
|
|
195
|
+
setViewDate(newDate);
|
|
196
|
+
onDateChange(newDate);
|
|
197
|
+
if (onViewChange) onViewChange("month");
|
|
198
|
+
};
|
|
199
|
+
const handleNext = () => {
|
|
200
|
+
const newDate = dateFns.addMonths(viewDate, 1);
|
|
201
|
+
setViewDate(newDate);
|
|
202
|
+
onDateChange(newDate);
|
|
203
|
+
if (onViewChange) onViewChange("month");
|
|
204
|
+
};
|
|
205
|
+
const handleDateClick = (day) => {
|
|
206
|
+
onDateChange(day);
|
|
207
|
+
if (onViewChange) onViewChange("day");
|
|
208
|
+
};
|
|
209
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn("px-4 w-[260px]", className) }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-between mb-4" }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-semibold text-foreground capitalize" }, dateFns.format(viewDate, "MMMM yyyy")), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center bg-muted/40 rounded-lg p-0.5" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
210
|
+
Button,
|
|
211
|
+
{
|
|
212
|
+
variant: "ghost",
|
|
213
|
+
size: "icon",
|
|
214
|
+
className: "h-7 w-7 rounded-md hover:bg-background/80 transition-all",
|
|
215
|
+
onClick: handlePrev,
|
|
216
|
+
"aria-label": "Previous month"
|
|
217
|
+
},
|
|
218
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronLeft, { className: "h-4 w-4" })
|
|
219
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
220
|
+
Button,
|
|
221
|
+
{
|
|
222
|
+
variant: "ghost",
|
|
223
|
+
size: "icon",
|
|
224
|
+
className: "h-7 w-7 rounded-md hover:bg-background/80 transition-all",
|
|
225
|
+
onClick: handleNext,
|
|
226
|
+
"aria-label": "Next month"
|
|
227
|
+
},
|
|
228
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronRight, { className: "h-4 w-4" })
|
|
229
|
+
))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "grid grid-cols-7 gap-y-2 text-center mb-2" }, weekDays.map((day, i) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: `${day}-${i}`, className: "text-[10px] text-muted-foreground/70 font-semibold uppercase" }, day))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "grid grid-cols-7 gap-y-1 text-center" }, days.map((day) => {
|
|
230
|
+
const isSelected = dateFns.isSameDay(day, currentDate);
|
|
231
|
+
const isCurrentMonth = dateFns.isSameMonth(day, viewDate);
|
|
232
|
+
const isTodayDate = dateFns.isToday(day);
|
|
233
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
234
|
+
"button",
|
|
235
|
+
{
|
|
236
|
+
key: day.toISOString(),
|
|
237
|
+
onClick: () => handleDateClick(day),
|
|
238
|
+
className: cn(
|
|
239
|
+
"h-8 w-8 mx-auto flex items-center justify-center text-xs rounded-xl transition-all duration-200 font-medium",
|
|
240
|
+
!isCurrentMonth && "text-muted-foreground/30",
|
|
241
|
+
isCurrentMonth && !isSelected && !isTodayDate && "text-foreground hover:bg-accent/80",
|
|
242
|
+
isSelected && "bg-primary text-primary-foreground shadow-md shadow-primary/30 scale-105",
|
|
243
|
+
!isSelected && isTodayDate && "bg-primary/10 text-primary ring-1 ring-primary/30"
|
|
244
|
+
)
|
|
245
|
+
},
|
|
246
|
+
dateFns.format(day, "d")
|
|
247
|
+
);
|
|
248
|
+
})));
|
|
249
|
+
};
|
|
250
|
+
var Sidebar = ({
|
|
251
|
+
currentDate,
|
|
252
|
+
onDateChange,
|
|
253
|
+
onViewChange,
|
|
254
|
+
onEventCreate,
|
|
255
|
+
timezone,
|
|
256
|
+
onTimezoneChange,
|
|
257
|
+
className,
|
|
258
|
+
readOnly,
|
|
259
|
+
calendars,
|
|
260
|
+
onCalendarToggle,
|
|
261
|
+
translations
|
|
262
|
+
}) => {
|
|
263
|
+
const [calendarsOpen, setCalendarsOpen] = React15.useState(true);
|
|
264
|
+
const [timezoneOpen, setTimezoneOpen] = React15.useState(false);
|
|
265
|
+
const [now, setNow] = React15.useState(null);
|
|
266
|
+
const [hasMounted, setHasMounted] = React15.useState(false);
|
|
267
|
+
React15.useEffect(() => {
|
|
268
|
+
setHasMounted(true);
|
|
269
|
+
setNow(/* @__PURE__ */ new Date());
|
|
270
|
+
const timer = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
|
|
271
|
+
return () => clearInterval(timer);
|
|
272
|
+
}, []);
|
|
273
|
+
const defaultCalendars = [
|
|
274
|
+
{ id: "1", label: "My Calendar", color: "#3b82f6", active: true },
|
|
275
|
+
{ id: "2", label: "Birthdays", color: "#10b981", active: true },
|
|
276
|
+
{ id: "3", label: "Tasks", color: "#6366f1", active: true }
|
|
277
|
+
];
|
|
278
|
+
const displayCalendars = calendars || defaultCalendars;
|
|
279
|
+
const getAcronym = (tz) => {
|
|
280
|
+
if (!tz || !now) return "LOC";
|
|
281
|
+
try {
|
|
282
|
+
return new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short" }).formatToParts(now).find((part) => part.type === "timeZoneName")?.value || "";
|
|
283
|
+
} catch (e) {
|
|
284
|
+
return "";
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const timezones = [
|
|
288
|
+
{ value: "", label: "Local Time", acronym: "LOC" },
|
|
289
|
+
{ value: "UTC", label: "UTC", acronym: "UTC" },
|
|
290
|
+
{ value: "America/New_York", label: "New York", acronym: "EST" },
|
|
291
|
+
{ value: "America/Chicago", label: "Chicago", acronym: "CST" },
|
|
292
|
+
{ value: "America/Denver", label: "Denver", acronym: "MST" },
|
|
293
|
+
{ value: "America/Los_Angeles", label: "Los Angeles", acronym: "PST" },
|
|
294
|
+
{ value: "Europe/London", label: "London", acronym: "GMT" },
|
|
295
|
+
{ value: "Europe/Paris", label: "Paris", acronym: "CET" },
|
|
296
|
+
{ value: "Europe/Berlin", label: "Berlin", acronym: "CET" },
|
|
297
|
+
{ value: "Asia/Dubai", label: "Dubai", acronym: "GST" },
|
|
298
|
+
{ value: "Asia/Tokyo", label: "Tokyo", acronym: "JST" },
|
|
299
|
+
{ value: "Asia/Singapore", label: "Singapore", acronym: "SGT" },
|
|
300
|
+
{ value: "Australia/Sydney", label: "Sydney", acronym: "AEDT" }
|
|
301
|
+
];
|
|
302
|
+
const formatTzLabel = (tz, showTime = true) => {
|
|
303
|
+
if (!hasMounted || !now || !showTime) {
|
|
304
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("span", null, tz.label);
|
|
305
|
+
}
|
|
306
|
+
let time = "";
|
|
307
|
+
let acronym = tz.acronym;
|
|
308
|
+
try {
|
|
309
|
+
if (!tz.value) {
|
|
310
|
+
time = dateFns.format(now, "HH:mm");
|
|
311
|
+
try {
|
|
312
|
+
acronym = new Intl.DateTimeFormat("en-US", { timeZoneName: "short" }).formatToParts(now).find((part) => part.type === "timeZoneName")?.value || "LOC";
|
|
313
|
+
} catch (e) {
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
const zDate = dateFnsTz.toZonedTime(now, tz.value);
|
|
317
|
+
time = dateFns.format(zDate, "HH:mm");
|
|
318
|
+
const dynAcronym = getAcronym(tz.value);
|
|
319
|
+
if (dynAcronym && !dynAcronym.includes("GMT") && !dynAcronym.includes("Time")) {
|
|
320
|
+
acronym = dynAcronym;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} catch (e) {
|
|
324
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("span", null, tz.label);
|
|
325
|
+
}
|
|
326
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex justify-between w-full" }, /* @__PURE__ */ React15__namespace.default.createElement("span", null, tz.label), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-muted-foreground ml-2 tabular-nums" }, time, " ", /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs opacity-75" }, "(", acronym, ")")));
|
|
327
|
+
};
|
|
328
|
+
const selectedTzObj = timezones.find((t) => t.value === (timezone || ""));
|
|
329
|
+
const selectedTimezoneLabel = selectedTzObj ? formatTzLabel(selectedTzObj) : translations?.localTime || "Local Time";
|
|
330
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn("flex flex-col w-[260px] overflow-x-hidden h-full bg-gradient-to-b scrollbar-hide from-background via-background to-muted/10 pt-4 pb-4 overflow-y-auto hidden lg:flex", className) }, !readOnly && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "px-4 mb-6" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
331
|
+
Button,
|
|
332
|
+
{
|
|
333
|
+
className: "w-full rounded-2xl shadow-lg bg-primary text-primary-foreground hover:bg-primary/90 h-12 gap-3 justify-center transition-all duration-300 hover:shadow-xl hover:shadow-primary/20 hover:scale-[1.02] active:scale-[0.98] font-semibold",
|
|
334
|
+
onClick: onEventCreate
|
|
335
|
+
},
|
|
336
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Plus, { className: "w-5 h-5" }),
|
|
337
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm" }, translations?.create || "Create")
|
|
338
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement(MiniCalendar, { currentDate, onDateChange, onViewChange }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 px-4 space-y-5 mt-5" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "bg-muted/20 rounded-2xl p-3 border-[0px] border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
339
|
+
"div",
|
|
340
|
+
{
|
|
341
|
+
className: "flex items-center justify-between cursor-pointer hover:bg-accent/50 p-2 -m-1 rounded-xl mb-2 transition-all duration-200",
|
|
342
|
+
onClick: () => setCalendarsOpen(!calendarsOpen)
|
|
343
|
+
},
|
|
344
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-semibold text-foreground" }, translations?.calendars || "Calendars"),
|
|
345
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-muted-foreground transition-transform duration-200", calendarsOpen && "rotate-180") })
|
|
346
|
+
), calendarsOpen && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "space-y-1" }, displayCalendars.map((cal) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
347
|
+
"div",
|
|
348
|
+
{
|
|
349
|
+
key: cal.id,
|
|
350
|
+
className: "flex items-center gap-3 py-2 px-2 hover:bg-accent/60 rounded-xl cursor-pointer group transition-all duration-200",
|
|
351
|
+
onClick: () => onCalendarToggle?.(cal.id, !(cal.active ?? true))
|
|
352
|
+
},
|
|
353
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative flex items-center justify-center" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
354
|
+
"input",
|
|
355
|
+
{
|
|
356
|
+
type: "checkbox",
|
|
357
|
+
checked: cal.active ?? true,
|
|
358
|
+
onChange: (e) => {
|
|
359
|
+
e.stopPropagation();
|
|
360
|
+
onCalendarToggle?.(cal.id, e.target.checked);
|
|
361
|
+
},
|
|
362
|
+
className: "peer h-5 w-5 rounded-md border-2 border-border/60 cursor-pointer appearance-none checked:border-transparent transition-all duration-200",
|
|
363
|
+
style: { "--primary-color": cal.color },
|
|
364
|
+
"data-cal-id": cal.id
|
|
365
|
+
}
|
|
366
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute inset-0 flex items-center justify-center pointer-events-none opacity-0 peer-checked:opacity-100 transition-opacity" }, /* @__PURE__ */ React15__namespace.default.createElement("svg", { className: "w-3.5 h-3.5 text-white", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3 }, /* @__PURE__ */ React15__namespace.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))), /* @__PURE__ */ React15__namespace.default.createElement("style", null, `
|
|
367
|
+
input[type="checkbox"][data-cal-id="${cal.id}"]:checked {
|
|
368
|
+
background-color: ${cal.color} !important;
|
|
369
|
+
border-color: ${cal.color} !important;
|
|
370
|
+
}
|
|
371
|
+
input[type="checkbox"][data-cal-id="${cal.id}"]:focus {
|
|
372
|
+
--tw-ring-color: ${cal.color}40 !important;
|
|
373
|
+
}
|
|
374
|
+
`)),
|
|
375
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 flex-1 min-w-0" }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm text-foreground/90 truncate font-medium" }, cal.label)),
|
|
376
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
377
|
+
"div",
|
|
378
|
+
{
|
|
379
|
+
className: "w-2 h-2 rounded-full opacity-60 group-hover:opacity-100 transition-opacity",
|
|
380
|
+
style: { backgroundColor: cal.color }
|
|
381
|
+
}
|
|
382
|
+
)
|
|
383
|
+
))))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "mt-auto px-4 pt-5" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "bg-muted/20 rounded-2xl p-3" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 mb-3" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "p-1.5 bg-primary/10 rounded-lg" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Globe, { className: "w-4 h-4 text-primary" })), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-semibold text-foreground" }, translations?.timezone || "Timezone")), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
384
|
+
"button",
|
|
385
|
+
{
|
|
386
|
+
onClick: () => setTimezoneOpen(!timezoneOpen),
|
|
387
|
+
className: "w-full flex items-center justify-between bg-blue-200/40 hover:bg-blue-200/80 rounded-xl py-2.5 pl-4 pr-3 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary/50 transition-all duration-200 text-left"
|
|
388
|
+
},
|
|
389
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 truncate mr-2 font-medium" }, selectedTimezoneLabel),
|
|
390
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-muted-foreground transition-transform duration-200", timezoneOpen && "rotate-180") })
|
|
391
|
+
), timezoneOpen && /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, null, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setTimezoneOpen(false) }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute bottom-full left-0 w-full mb-2 bg-background rounded-xl shadow-2xl z-50 max-h-[260px] overflow-y-auto p-1.5 animate-in fade-in zoom-in-95 duration-200 backdrop-blur-none" }, timezones.map((tz) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
392
|
+
"div",
|
|
393
|
+
{
|
|
394
|
+
key: tz.value,
|
|
395
|
+
className: cn(
|
|
396
|
+
"px-3 py-2.5 text-sm rounded-lg cursor-pointer transition-all duration-200",
|
|
397
|
+
(timezone || "") === tz.value ? "bg-primary text-primary-foreground font-semibold" : "text-foreground hover:bg-accent/80"
|
|
398
|
+
),
|
|
399
|
+
onClick: () => {
|
|
400
|
+
onTimezoneChange?.(tz.value);
|
|
401
|
+
setTimezoneOpen(false);
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
formatTzLabel(tz)
|
|
405
|
+
))))))));
|
|
406
|
+
};
|
|
407
|
+
var DraggableEvent = ({ event, children, className, style: propStyle, ...props }) => {
|
|
408
|
+
const { attributes, listeners, setNodeRef, transform, isDragging } = core.useDraggable({
|
|
409
|
+
id: event.id,
|
|
410
|
+
data: { event }
|
|
411
|
+
});
|
|
412
|
+
const style = {
|
|
413
|
+
...propStyle,
|
|
414
|
+
transform: utilities.CSS.Translate.toString(transform),
|
|
415
|
+
zIndex: isDragging ? 100 : propStyle?.zIndex,
|
|
416
|
+
// When dragging, we want the ORIGINAL element to be almost invisible (or fully invisible)
|
|
417
|
+
// so that the DragOverlay (the "ghost") is the only thing the user focuses on.
|
|
418
|
+
// If we set opacity: 0, it disappears. If 0.1, it's a faint placeholder.
|
|
419
|
+
// The user asked for "Make the entire dragged item transparent".
|
|
420
|
+
// Usually this means the original item should be faded out while the drag overlay is moving.
|
|
421
|
+
opacity: isDragging ? 0 : propStyle?.opacity
|
|
422
|
+
// Hiding original element completely
|
|
423
|
+
};
|
|
424
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
425
|
+
"div",
|
|
426
|
+
{
|
|
427
|
+
ref: setNodeRef,
|
|
428
|
+
style,
|
|
429
|
+
...listeners,
|
|
430
|
+
...attributes,
|
|
431
|
+
...props,
|
|
432
|
+
className: cn("touch-none", className)
|
|
433
|
+
},
|
|
434
|
+
children
|
|
435
|
+
);
|
|
436
|
+
};
|
|
437
|
+
var DroppableCell = ({ id, date, resourceId, children, className, style, onClick }) => {
|
|
438
|
+
const { isOver, setNodeRef } = core.useDroppable({
|
|
439
|
+
id,
|
|
440
|
+
data: { date, resourceId }
|
|
441
|
+
});
|
|
442
|
+
const minutes = date.getMinutes();
|
|
443
|
+
const quarterClass = minutes === 0 ? "hover:bg-blue-50/50 dark:hover:bg-blue-900/10" : minutes === 15 ? "hover:bg-blue-50/80 dark:hover:bg-blue-900/20" : minutes === 30 ? "hover:bg-blue-100/50 dark:hover:bg-blue-900/30" : "hover:bg-blue-100/80 dark:hover:bg-blue-900/40";
|
|
444
|
+
const activeQuarterClass = minutes === 0 ? "bg-blue-50/50 dark:bg-blue-900/10 ring-2 ring-primary ring-inset" : minutes === 15 ? "bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-primary ring-inset" : minutes === 30 ? "bg-blue-100/50 dark:bg-blue-900/30 ring-2 ring-primary ring-inset" : "bg-blue-100/80 dark:bg-blue-900/40 ring-2 ring-primary ring-inset";
|
|
445
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
446
|
+
"div",
|
|
447
|
+
{
|
|
448
|
+
ref: setNodeRef,
|
|
449
|
+
className: cn(
|
|
450
|
+
className,
|
|
451
|
+
quarterClass,
|
|
452
|
+
isOver && activeQuarterClass
|
|
453
|
+
),
|
|
454
|
+
style,
|
|
455
|
+
onClick
|
|
456
|
+
},
|
|
457
|
+
children
|
|
458
|
+
);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/views/MonthView.tsx
|
|
462
|
+
var EventItem = React15__namespace.default.memo(({ event, onEventClick }) => /* @__PURE__ */ React15__namespace.default.createElement(DraggableEvent, { event }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
463
|
+
"div",
|
|
464
|
+
{
|
|
465
|
+
className: cn(
|
|
466
|
+
"text-xs px-2.5 py-1.5 rounded-lg truncate cursor-pointer shadow-sm transition-all duration-200",
|
|
467
|
+
"hover:shadow-md hover:scale-[1.02] hover:z-10",
|
|
468
|
+
!event.color && "bg-primary/10 text-primary hover:bg-primary/15 border-[0.5px] border-primary/20"
|
|
469
|
+
),
|
|
470
|
+
style: event.color ? {
|
|
471
|
+
backgroundColor: `${event.color}20`,
|
|
472
|
+
color: event.color,
|
|
473
|
+
borderLeft: `3px solid ${event.color}`
|
|
474
|
+
} : void 0,
|
|
475
|
+
onClick: (e) => {
|
|
476
|
+
e.stopPropagation();
|
|
477
|
+
onEventClick?.(event);
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "font-medium" }, event.title)
|
|
481
|
+
)));
|
|
482
|
+
EventItem.displayName = "EventItem";
|
|
483
|
+
var MonthView = ({
|
|
484
|
+
currentDate,
|
|
485
|
+
events,
|
|
486
|
+
onEventClick,
|
|
487
|
+
onDateClick,
|
|
488
|
+
timezone,
|
|
489
|
+
locale
|
|
490
|
+
}) => {
|
|
491
|
+
const days = React15.useMemo(() => getMonthGrid(currentDate), [currentDate]);
|
|
492
|
+
const weekDays = React15.useMemo(() => {
|
|
493
|
+
const start = dateFns.startOfWeek(currentDate, { weekStartsOn: 1 });
|
|
494
|
+
const end = dateFns.endOfWeek(currentDate, { weekStartsOn: 1 });
|
|
495
|
+
return dateFns.eachDayOfInterval({ start, end });
|
|
496
|
+
}, [currentDate]);
|
|
497
|
+
const getZonedDate = React15.useCallback((date) => {
|
|
498
|
+
return timezone ? dateFnsTz.toZonedTime(date, timezone) : date;
|
|
499
|
+
}, [timezone]);
|
|
500
|
+
const eventsByDay = React15.useMemo(() => {
|
|
501
|
+
const map = /* @__PURE__ */ new Map();
|
|
502
|
+
events.forEach((event) => {
|
|
503
|
+
const zonedStart = getZonedDate(event.start);
|
|
504
|
+
const key = dateFns.format(zonedStart, "yyyy-MM-dd");
|
|
505
|
+
if (!map.has(key)) {
|
|
506
|
+
map.set(key, []);
|
|
507
|
+
}
|
|
508
|
+
map.get(key).push(event);
|
|
509
|
+
});
|
|
510
|
+
return map;
|
|
511
|
+
}, [events, getZonedDate]);
|
|
512
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] scrollbar-hide border-border/50 rounded-2xl overflow-hidden min-w-[800px] md:min-w-0 shadow-sm" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "overflow-y-auto flex-1 relative" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "grid grid-cols-7 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/30 via-muted/40 to-muted/30 sticky top-0 z-20 backdrop-blur-sm" }, weekDays.map((day) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
513
|
+
"div",
|
|
514
|
+
{
|
|
515
|
+
key: day.toISOString(),
|
|
516
|
+
className: "py-3 text-center text-xs font-semibold text-muted-foreground border-r-[0.5px] border-border/30 last:border-r-0 uppercase tracking-wider"
|
|
517
|
+
},
|
|
518
|
+
dateFns.format(day, "EEE", { locale })
|
|
519
|
+
))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "grid grid-cols-7", style: { gridAutoRows: "130px" } }, days.map((day) => {
|
|
520
|
+
const dayKey = dateFns.format(day, "yyyy-MM-dd");
|
|
521
|
+
const dayEvents = eventsByDay.get(dayKey) || [];
|
|
522
|
+
const isCurrentMonth = dateFns.isSameMonth(day, currentDate);
|
|
523
|
+
const cellId = day.toISOString();
|
|
524
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
525
|
+
DroppableCell,
|
|
526
|
+
{
|
|
527
|
+
key: cellId,
|
|
528
|
+
id: cellId,
|
|
529
|
+
date: day,
|
|
530
|
+
className: cn(
|
|
531
|
+
"h-[130px] p-2 border-b-[0.5px] border-r-[0.5px] border-border/30 last:border-r-0 relative transition-all duration-200 hover:bg-accent/5 flex flex-col gap-1.5 overflow-hidden group",
|
|
532
|
+
!isCurrentMonth && "bg-muted/5 text-muted-foreground/60",
|
|
533
|
+
dateFns.isToday(day) && "bg-primary/5 ring-1 ring-inset ring-primary/20"
|
|
534
|
+
),
|
|
535
|
+
onClick: () => onDateClick?.(day)
|
|
536
|
+
},
|
|
537
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn(
|
|
538
|
+
"text-sm font-semibold w-7 h-7 flex items-center justify-center rounded-full transition-all duration-200",
|
|
539
|
+
dateFns.isToday(day) ? "bg-primary text-primary-foreground shadow-md shadow-primary/30" : "group-hover:bg-accent"
|
|
540
|
+
) }, dateFns.format(day, "d", { locale })), dayEvents.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-[10px] font-medium text-muted-foreground/60 bg-muted/50 px-1.5 py-0.5 rounded-full" }, dayEvents.length)),
|
|
541
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 flex flex-col gap-1 scrollbar-hide overflow-y-auto overflow-x-hidden" }, dayEvents.slice(0, 4).map((event) => /* @__PURE__ */ React15__namespace.default.createElement(EventItem, { key: `${event.id}-${dayKey}`, event, onEventClick })), dayEvents.length > 4 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-[10px] text-primary font-semibold text-center py-1 px-2 rounded-md bg-primary/5 hover:bg-primary/10 cursor-pointer transition-colors" }, "+", dayEvents.length - 4, " more"))
|
|
542
|
+
);
|
|
543
|
+
}))));
|
|
544
|
+
};
|
|
545
|
+
var ResizableEvent = ({
|
|
546
|
+
event,
|
|
547
|
+
hourHeight,
|
|
548
|
+
minDuration = 15,
|
|
549
|
+
onResize,
|
|
550
|
+
children,
|
|
551
|
+
className,
|
|
552
|
+
style,
|
|
553
|
+
readonly
|
|
554
|
+
}) => {
|
|
555
|
+
const [isResizing, setIsResizing] = React15.useState(false);
|
|
556
|
+
const [resizeHeight, setResizeHeight] = React15.useState(null);
|
|
557
|
+
const containerRef = React15.useRef(null);
|
|
558
|
+
const startYRef = React15.useRef(0);
|
|
559
|
+
const startHeightRef = React15.useRef(0);
|
|
560
|
+
const handleResizeStart = React15.useCallback((e) => {
|
|
561
|
+
e.preventDefault();
|
|
562
|
+
e.stopPropagation();
|
|
563
|
+
const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
|
|
564
|
+
startYRef.current = clientY;
|
|
565
|
+
startHeightRef.current = containerRef.current?.offsetHeight || 0;
|
|
566
|
+
setIsResizing(true);
|
|
567
|
+
const handleMove = (moveEvent) => {
|
|
568
|
+
const moveClientY = "touches" in moveEvent ? moveEvent.touches[0].clientY : moveEvent.clientY;
|
|
569
|
+
const deltaY = moveClientY - startYRef.current;
|
|
570
|
+
const snapInterval = hourHeight / 4;
|
|
571
|
+
const snappedDelta = Math.round(deltaY / snapInterval) * snapInterval;
|
|
572
|
+
const newHeight = Math.max(
|
|
573
|
+
minDuration / 60 * hourHeight,
|
|
574
|
+
// Minimum height based on minDuration
|
|
575
|
+
startHeightRef.current + snappedDelta
|
|
576
|
+
);
|
|
577
|
+
setResizeHeight(newHeight);
|
|
578
|
+
};
|
|
579
|
+
const handleEnd = () => {
|
|
580
|
+
setIsResizing(false);
|
|
581
|
+
if (resizeHeight !== null && containerRef.current && onResize) {
|
|
582
|
+
const originalHeight = startHeightRef.current;
|
|
583
|
+
const heightDiff = (resizeHeight || originalHeight) - originalHeight;
|
|
584
|
+
const minutesDiff = heightDiff / hourHeight * 60;
|
|
585
|
+
const newEnd = new Date(event.end);
|
|
586
|
+
newEnd.setMinutes(newEnd.getMinutes() + minutesDiff);
|
|
587
|
+
if (newEnd > event.start) {
|
|
588
|
+
onResize(event, newEnd);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
setResizeHeight(null);
|
|
592
|
+
document.removeEventListener("mousemove", handleMove);
|
|
593
|
+
document.removeEventListener("mouseup", handleEnd);
|
|
594
|
+
document.removeEventListener("touchmove", handleMove);
|
|
595
|
+
document.removeEventListener("touchend", handleEnd);
|
|
596
|
+
};
|
|
597
|
+
document.addEventListener("mousemove", handleMove);
|
|
598
|
+
document.addEventListener("mouseup", handleEnd);
|
|
599
|
+
document.addEventListener("touchmove", handleMove, { passive: false });
|
|
600
|
+
document.addEventListener("touchend", handleEnd);
|
|
601
|
+
}, [event, hourHeight, minDuration, onResize, resizeHeight]);
|
|
602
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
603
|
+
"div",
|
|
604
|
+
{
|
|
605
|
+
ref: containerRef,
|
|
606
|
+
className: cn("relative group", className),
|
|
607
|
+
style: {
|
|
608
|
+
...style,
|
|
609
|
+
height: resizeHeight !== null ? `${resizeHeight}px` : style?.height
|
|
610
|
+
}
|
|
611
|
+
},
|
|
612
|
+
children,
|
|
613
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
614
|
+
"div",
|
|
615
|
+
{
|
|
616
|
+
className: cn(
|
|
617
|
+
readonly ? "cursor-default" : "cursor-ns-resize group-hover:opacity-100",
|
|
618
|
+
"absolute bottom-0 left-0 right-0 h-3 z-20 flex items-center justify-center",
|
|
619
|
+
"opacity-0 transition-opacity",
|
|
620
|
+
isResizing && "opacity-100"
|
|
621
|
+
),
|
|
622
|
+
onMouseDown: readonly ? void 0 : handleResizeStart,
|
|
623
|
+
onTouchStart: readonly ? void 0 : handleResizeStart
|
|
624
|
+
},
|
|
625
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn(
|
|
626
|
+
"w-8 h-1 rounded-full transition-all",
|
|
627
|
+
isResizing ? "bg-primary" : "bg-muted-foreground/30 group-hover:bg-primary/50"
|
|
628
|
+
) })
|
|
629
|
+
),
|
|
630
|
+
isResizing && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute inset-0 border-2 border-primary border-dashed rounded-md pointer-events-none bg-primary/5" })
|
|
631
|
+
);
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
// src/views/WeekView.tsx
|
|
635
|
+
var WeekView = ({
|
|
636
|
+
currentDate,
|
|
637
|
+
events,
|
|
638
|
+
onEventClick,
|
|
639
|
+
onTimeSlotClick,
|
|
640
|
+
onEventResize,
|
|
641
|
+
timezone,
|
|
642
|
+
locale,
|
|
643
|
+
readonly
|
|
644
|
+
}) => {
|
|
645
|
+
const start = dateFns.startOfWeek(currentDate, { weekStartsOn: 1 });
|
|
646
|
+
const end = dateFns.endOfWeek(currentDate, { weekStartsOn: 1 });
|
|
647
|
+
const weekDays = dateFns.eachDayOfInterval({ start, end });
|
|
648
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
649
|
+
const hourHeight = 60;
|
|
650
|
+
const scrollContainerRef = React15__namespace.default.useRef(null);
|
|
651
|
+
const [now, setNow] = React15__namespace.default.useState(/* @__PURE__ */ new Date());
|
|
652
|
+
React15__namespace.default.useEffect(() => {
|
|
653
|
+
const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
|
|
654
|
+
return () => clearInterval(interval);
|
|
655
|
+
}, []);
|
|
656
|
+
React15__namespace.default.useEffect(() => {
|
|
657
|
+
if (scrollContainerRef.current) {
|
|
658
|
+
const scrollToActualTime = () => {
|
|
659
|
+
const currentHour = now.getHours();
|
|
660
|
+
const offsetHour = 4;
|
|
661
|
+
return (currentHour - offsetHour) * hourHeight;
|
|
662
|
+
};
|
|
663
|
+
scrollContainerRef.current.scrollTop = scrollToActualTime();
|
|
664
|
+
}
|
|
665
|
+
}, []);
|
|
666
|
+
const getZonedDate = (date) => {
|
|
667
|
+
return timezone ? dateFnsTz.toZonedTime(date, timezone) : date;
|
|
668
|
+
};
|
|
669
|
+
const zonedNow = getZonedDate(/* @__PURE__ */ new Date());
|
|
670
|
+
const getTimezoneDisplay = (tz) => {
|
|
671
|
+
const date = now;
|
|
672
|
+
let displayTime = "";
|
|
673
|
+
let acronym = "";
|
|
674
|
+
if (!tz) {
|
|
675
|
+
displayTime = dateFns.format(date, "HH:mm");
|
|
676
|
+
try {
|
|
677
|
+
acronym = new Intl.DateTimeFormat("en-US", { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value || "";
|
|
678
|
+
} catch (e) {
|
|
679
|
+
acronym = "LOC";
|
|
680
|
+
}
|
|
681
|
+
} else {
|
|
682
|
+
try {
|
|
683
|
+
const zDate = dateFnsTz.toZonedTime(date, tz);
|
|
684
|
+
displayTime = dateFns.format(zDate, "HH:mm");
|
|
685
|
+
acronym = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value || "";
|
|
686
|
+
} catch (e) {
|
|
687
|
+
displayTime = dateFns.format(date, "HH:mm");
|
|
688
|
+
acronym = tz;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col items-center justify-center leading-tight" }, /* @__PURE__ */ React15__namespace.default.createElement("span", null, displayTime), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-[10px] opacity-75" }, "(", acronym, ")"));
|
|
692
|
+
};
|
|
693
|
+
const timeFormat = locale?.code === "fr" ? "H:mm" : "h a";
|
|
694
|
+
const eventTimeFormat = locale?.code === "fr" ? "H:mm" : "h:mm a";
|
|
695
|
+
const nowFormat = locale?.code === "fr" ? "H:mm" : "h:mm";
|
|
696
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden min-w-[800px] md:min-w-0 shadow-sm" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
697
|
+
"div",
|
|
698
|
+
{
|
|
699
|
+
ref: scrollContainerRef,
|
|
700
|
+
className: "flex-1 overflow-y-auto scrollbar-hide relative bg-background scroll-smooth",
|
|
701
|
+
style: { scrollbarGutter: "stable" }
|
|
702
|
+
},
|
|
703
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 z-20 sticky top-0 backdrop-blur-sm" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-none p-3 text-center text-xs font-semibold text-muted-foreground w-16 flex items-center justify-center border-r-[0.5px] border-border/30 bg-muted/10" }, getTimezoneDisplay(timezone)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 grid grid-cols-7" }, weekDays.map((day, index) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: day.toISOString(), className: cn("py-3 px-2 text-center", index > 0 && "border-l-[0.5px] border-border/30") }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1.5" }, dateFns.format(day, "EEE", { locale })), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn(
|
|
704
|
+
"w-9 h-9 flex items-center justify-center rounded-xl mx-auto text-sm font-semibold transition-all duration-200",
|
|
705
|
+
dateFns.isToday(day) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30 scale-110" : "text-foreground hover:bg-accent/80"
|
|
706
|
+
) }, dateFns.format(day, "d", { locale })))))),
|
|
707
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
708
|
+
"div",
|
|
709
|
+
{
|
|
710
|
+
className: "flex min-w-full relative",
|
|
711
|
+
style: { height: hours.length * hourHeight }
|
|
712
|
+
},
|
|
713
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-none w-16 border-r-[0.5px] border-border/30 relative bg-muted/5" }, hours.map((hour) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
714
|
+
"div",
|
|
715
|
+
{
|
|
716
|
+
key: hour,
|
|
717
|
+
className: "relative w-full text-[11px] text-muted-foreground/80 text-right pr-3 font-medium tabular-nums box-border",
|
|
718
|
+
style: { height: hourHeight }
|
|
719
|
+
},
|
|
720
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "block -translate-y-1/2" }, hour !== 0 && dateFns.format((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale }))
|
|
721
|
+
))),
|
|
722
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 grid grid-cols-7 relative" }, weekDays.map((day, dayIndex) => {
|
|
723
|
+
const dayEvents = events.filter((e) => {
|
|
724
|
+
const zonedStart = getZonedDate(e.start);
|
|
725
|
+
return dateFns.isSameDay(zonedStart, day);
|
|
726
|
+
});
|
|
727
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { key: day.toISOString(), className: cn("relative h-full", dayIndex > 0 && "border-l-[0.5px] border-border/30") }, hours.map((hour) => {
|
|
728
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
729
|
+
"div",
|
|
730
|
+
{
|
|
731
|
+
key: hour,
|
|
732
|
+
className: "w-full border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
|
|
733
|
+
style: { height: hourHeight }
|
|
734
|
+
},
|
|
735
|
+
[0, 15, 30, 45].map((minute) => {
|
|
736
|
+
const cellDate = new Date(day);
|
|
737
|
+
cellDate.setHours(hour, minute, 0, 0);
|
|
738
|
+
const cellId = cellDate.toISOString();
|
|
739
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
740
|
+
DroppableCell,
|
|
741
|
+
{
|
|
742
|
+
key: minute,
|
|
743
|
+
id: cellId,
|
|
744
|
+
date: cellDate,
|
|
745
|
+
className: "w-full absolute left-0 right-0 z-0 transition-colors",
|
|
746
|
+
style: {
|
|
747
|
+
height: "25%",
|
|
748
|
+
top: `${minute / 60 * 100}%`
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
752
|
+
"div",
|
|
753
|
+
{
|
|
754
|
+
className: "w-full h-full bg-transparent cursor-pointer",
|
|
755
|
+
onClick: () => onTimeSlotClick?.(cellDate)
|
|
756
|
+
}
|
|
757
|
+
)
|
|
758
|
+
);
|
|
759
|
+
})
|
|
760
|
+
);
|
|
761
|
+
}), dayEvents.map((event) => {
|
|
762
|
+
const overlappingEvents = dayEvents.filter((e) => {
|
|
763
|
+
if (e.id === event.id) return false;
|
|
764
|
+
const s1 = getZonedDate(event.start).getTime();
|
|
765
|
+
const e1 = getZonedDate(event.end).getTime();
|
|
766
|
+
const s2 = getZonedDate(e.start).getTime();
|
|
767
|
+
const e2 = getZonedDate(e.end).getTime();
|
|
768
|
+
return s1 < e2 && e1 > s2;
|
|
769
|
+
});
|
|
770
|
+
const group = [event, ...overlappingEvents].sort(
|
|
771
|
+
(a, b) => getZonedDate(a.start).getTime() - getZonedDate(b.start).getTime() || (a.id > b.id ? 1 : -1)
|
|
772
|
+
// stable sort
|
|
773
|
+
);
|
|
774
|
+
const index = group.findIndex((e) => e.id === event.id);
|
|
775
|
+
const count = group.length;
|
|
776
|
+
const widthPercent = 100 / count;
|
|
777
|
+
const leftPercent = index * widthPercent;
|
|
778
|
+
const zonedEventStart = getZonedDate(event.start);
|
|
779
|
+
const zonedEventEnd = getZonedDate(event.end);
|
|
780
|
+
const startMinutes = zonedEventStart.getHours() * 60 + zonedEventStart.getMinutes();
|
|
781
|
+
const durationMinutes = dateFns.differenceInMinutes(zonedEventEnd, zonedEventStart);
|
|
782
|
+
const top = startMinutes / 60 * hourHeight;
|
|
783
|
+
const height = durationMinutes / 60 * hourHeight;
|
|
784
|
+
const isShortEvent = durationMinutes < 60;
|
|
785
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
786
|
+
DraggableEvent,
|
|
787
|
+
{
|
|
788
|
+
key: `${event.id}-${day.toISOString()}`,
|
|
789
|
+
event,
|
|
790
|
+
className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
|
|
791
|
+
style: {
|
|
792
|
+
top: `${top}px`,
|
|
793
|
+
height: `${Math.max(height, 20)}px`,
|
|
794
|
+
left: `${leftPercent}%`,
|
|
795
|
+
width: `${widthPercent}%`,
|
|
796
|
+
// Add minimal spacing between overlapping events
|
|
797
|
+
paddingRight: count > 1 ? "2px" : "0"
|
|
798
|
+
}
|
|
799
|
+
},
|
|
800
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
801
|
+
ResizableEvent,
|
|
802
|
+
{
|
|
803
|
+
readonly,
|
|
804
|
+
event,
|
|
805
|
+
hourHeight,
|
|
806
|
+
onResize: onEventResize,
|
|
807
|
+
className: "h-full",
|
|
808
|
+
style: { height: "100%" }
|
|
809
|
+
},
|
|
810
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
811
|
+
"div",
|
|
812
|
+
{
|
|
813
|
+
className: cn(
|
|
814
|
+
"rounded-md border shadow-sm transition-all hover:shadow-md group overflow-hidden relative",
|
|
815
|
+
"glass",
|
|
816
|
+
readonly ? "cursor-default" : "cursor-grab active:cursor-grabbing",
|
|
817
|
+
!event.color && "border-primary/20 bg-primary/10",
|
|
818
|
+
isShortEvent ? "px-1 flex items-center justify-center" : "p-2",
|
|
819
|
+
// Add active border for overlapped events to distinguish them
|
|
820
|
+
count > 1 && "border-l-4 border-l-primary/50"
|
|
821
|
+
),
|
|
822
|
+
style: {
|
|
823
|
+
height: "100%",
|
|
824
|
+
backgroundColor: event.color ? `${event.color}15` : void 0,
|
|
825
|
+
borderColor: event.color ? `${event.color}40` : void 0,
|
|
826
|
+
borderLeftWidth: "3px",
|
|
827
|
+
borderLeftColor: event.color || "var(--primary)"
|
|
828
|
+
},
|
|
829
|
+
onClick: (e) => {
|
|
830
|
+
e.stopPropagation();
|
|
831
|
+
onEventClick?.(event);
|
|
832
|
+
},
|
|
833
|
+
title: count > 1 ? `${event.title} (${index + 1}/${count})` : void 0
|
|
834
|
+
},
|
|
835
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col h-full overflow-hidden w-full" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn(
|
|
836
|
+
"font-semibold truncate text-foreground/90 leading-tight",
|
|
837
|
+
isShortEvent ? "text-xs text-center" : "text-xs"
|
|
838
|
+
) }, event.title), !isShortEvent && /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, null, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-[10px] text-muted-foreground truncate mt-0.5 font-medium leading-tight" }, dateFns.format(zonedEventStart, eventTimeFormat, { locale }), " - ", dateFns.format(zonedEventEnd, eventTimeFormat, { locale })), event.description && height > 50 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-[10px] text-muted-foreground/80 truncate mt-1 leading-tight opacity-80" }, event.description)), count > 1 && !isShortEvent && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute top-1 right-1 bg-background/80 backdrop-blur-sm rounded-full w-4 h-4 flex items-center justify-center text-[9px] font-bold text-muted-foreground border border-border shadow-sm" }, count))
|
|
839
|
+
)
|
|
840
|
+
)
|
|
841
|
+
);
|
|
842
|
+
}), dateFns.isToday(day) && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
843
|
+
"div",
|
|
844
|
+
{
|
|
845
|
+
className: "absolute left-0 right-0 z-20 pointer-events-none flex items-center",
|
|
846
|
+
style: {
|
|
847
|
+
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
|
|
851
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute -left-1.5 w-3 h-3 bg-primary rounded-full shadow-lg shadow-primary/40 ring-2 ring-background animate-pulse" })
|
|
852
|
+
));
|
|
853
|
+
}))
|
|
854
|
+
),
|
|
855
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
856
|
+
"div",
|
|
857
|
+
{
|
|
858
|
+
className: "absolute left-0 w-16 pointer-events-none z-30 flex justify-end pr-2",
|
|
859
|
+
style: {
|
|
860
|
+
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight + 80}px`
|
|
861
|
+
}
|
|
862
|
+
},
|
|
863
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-[10px] font-bold text-primary-foreground bg-primary px-1.5 py-0.5 rounded-md shadow-md -translate-y-1/2 backdrop-blur-none" }, dateFns.format(zonedNow, nowFormat, { locale }))
|
|
864
|
+
)
|
|
865
|
+
));
|
|
866
|
+
};
|
|
867
|
+
var DayView = ({
|
|
868
|
+
currentDate,
|
|
869
|
+
events,
|
|
870
|
+
onEventClick,
|
|
871
|
+
onTimeSlotClick,
|
|
872
|
+
onEventResize,
|
|
873
|
+
timezone,
|
|
874
|
+
locale,
|
|
875
|
+
readonly
|
|
876
|
+
}) => {
|
|
877
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
878
|
+
const hourHeight = 80;
|
|
879
|
+
const scrollContainerRef = React15__namespace.default.useRef(null);
|
|
880
|
+
const [now, setNow] = React15__namespace.default.useState(/* @__PURE__ */ new Date());
|
|
881
|
+
React15__namespace.default.useEffect(() => {
|
|
882
|
+
const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
|
|
883
|
+
return () => clearInterval(interval);
|
|
884
|
+
}, []);
|
|
885
|
+
React15__namespace.default.useEffect(() => {
|
|
886
|
+
if (scrollContainerRef.current) {
|
|
887
|
+
const scrollToActualTime = () => {
|
|
888
|
+
const currentHour = now.getHours();
|
|
889
|
+
const offsetHour = 3;
|
|
890
|
+
return (currentHour - offsetHour) * hourHeight;
|
|
891
|
+
};
|
|
892
|
+
scrollContainerRef.current.scrollTop = scrollToActualTime();
|
|
893
|
+
}
|
|
894
|
+
}, []);
|
|
895
|
+
const getZonedDate = (date) => {
|
|
896
|
+
return timezone ? dateFnsTz.toZonedTime(date, timezone) : date;
|
|
897
|
+
};
|
|
898
|
+
const zonedNow = getZonedDate(now);
|
|
899
|
+
const dayEvents = events.filter((e) => {
|
|
900
|
+
const zonedStart = getZonedDate(e.start);
|
|
901
|
+
return dateFns.isSameDay(zonedStart, currentDate);
|
|
902
|
+
});
|
|
903
|
+
const timeFormat = locale?.code === "fr" ? "H:mm" : "h a";
|
|
904
|
+
const eventTimeFormat = locale?.code === "fr" ? "H:mm" : "h:mm a";
|
|
905
|
+
const nowFormat = locale?.code === "fr" ? "H:mm" : "h:mm a";
|
|
906
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden shadow-sm" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "px-6 py-4 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 text-center shrink-0" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-center gap-3" }, /* @__PURE__ */ React15__namespace.default.createElement("h2", { className: "text-xl font-semibold capitalize text-foreground" }, dateFns.format(currentDate, "EEEE, MMMM d, yyyy", { locale })), dateFns.isToday(currentDate) && /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs font-semibold bg-primary text-primary-foreground px-3 py-1 rounded-full shadow-md shadow-primary/20" }, "Today"))), /* @__PURE__ */ React15__namespace.default.createElement("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto relative" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex relative", style: { height: hours.length * hourHeight } }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-20 bg-muted/5 border-r-[0.5px] border-border/30 relative" }, hours.map((hour) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
907
|
+
"div",
|
|
908
|
+
{
|
|
909
|
+
key: hour,
|
|
910
|
+
className: "relative w-full",
|
|
911
|
+
style: { height: hourHeight }
|
|
912
|
+
},
|
|
913
|
+
hour !== 0 && /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "absolute w-full text-center -top-3 left-1/2 -translate-x-1/2 text-[11px] text-muted-foreground/80 font-medium tabular-nums bg-background px-1.5 py-0.5 rounded-md" }, dateFns.format((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale }))
|
|
914
|
+
)), dateFns.isToday(currentDate) && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
915
|
+
"div",
|
|
916
|
+
{
|
|
917
|
+
className: "absolute left-0 w-full pointer-events-none z-30 flex justify-end pr-2",
|
|
918
|
+
style: {
|
|
919
|
+
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
|
|
920
|
+
}
|
|
921
|
+
},
|
|
922
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-[10px] font-bold text-white bg-primary px-2 py-1 rounded-lg shadow-md shadow-primary/30 -translate-y-1/2" }, dateFns.format(zonedNow, nowFormat, { locale }))
|
|
923
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 relative" }, hours.map((hour) => {
|
|
924
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
925
|
+
"div",
|
|
926
|
+
{
|
|
927
|
+
key: hour,
|
|
928
|
+
className: "border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
|
|
929
|
+
style: { height: hourHeight }
|
|
930
|
+
},
|
|
931
|
+
[0, 15, 30, 45].map((minute) => {
|
|
932
|
+
const cellDate = new Date(currentDate);
|
|
933
|
+
cellDate.setHours(hour, minute, 0, 0);
|
|
934
|
+
const cellId = cellDate.toISOString();
|
|
935
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
936
|
+
DroppableCell,
|
|
937
|
+
{
|
|
938
|
+
key: minute,
|
|
939
|
+
id: cellId,
|
|
940
|
+
date: cellDate,
|
|
941
|
+
className: "w-full absolute left-0 right-0 z-0 transition-colors",
|
|
942
|
+
style: {
|
|
943
|
+
height: "25%",
|
|
944
|
+
top: `${minute / 60 * 100}%`
|
|
945
|
+
}
|
|
946
|
+
},
|
|
947
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
948
|
+
"div",
|
|
949
|
+
{
|
|
950
|
+
className: "h-full w-full bg-transparent cursor-pointer",
|
|
951
|
+
onClick: () => onTimeSlotClick?.(cellDate)
|
|
952
|
+
}
|
|
953
|
+
)
|
|
954
|
+
);
|
|
955
|
+
})
|
|
956
|
+
);
|
|
957
|
+
}), dayEvents.map((event) => {
|
|
958
|
+
const overlappingEvents = dayEvents.filter((e) => {
|
|
959
|
+
if (e.id === event.id) return false;
|
|
960
|
+
const s1 = getZonedDate(event.start).getTime();
|
|
961
|
+
const e1 = getZonedDate(event.end).getTime();
|
|
962
|
+
const s2 = getZonedDate(e.start).getTime();
|
|
963
|
+
const e2 = getZonedDate(e.end).getTime();
|
|
964
|
+
return s1 < e2 && e1 > s2;
|
|
965
|
+
});
|
|
966
|
+
const group = [event, ...overlappingEvents].sort(
|
|
967
|
+
(a, b) => getZonedDate(a.start).getTime() - getZonedDate(b.start).getTime() || (a.id > b.id ? 1 : -1)
|
|
968
|
+
);
|
|
969
|
+
const index = group.findIndex((e) => e.id === event.id);
|
|
970
|
+
const count = group.length;
|
|
971
|
+
const widthPercent = 100 / count;
|
|
972
|
+
const leftPercent = index * widthPercent;
|
|
973
|
+
const zonedStart = getZonedDate(event.start);
|
|
974
|
+
const zonedEnd = getZonedDate(event.end);
|
|
975
|
+
const startMinutes = zonedStart.getHours() * 60 + zonedStart.getMinutes();
|
|
976
|
+
const durationMinutes = dateFns.differenceInMinutes(zonedEnd, zonedStart);
|
|
977
|
+
const top = startMinutes / 60 * hourHeight;
|
|
978
|
+
const height = durationMinutes / 60 * hourHeight;
|
|
979
|
+
const isShortEvent = durationMinutes < 45;
|
|
980
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
981
|
+
DraggableEvent,
|
|
982
|
+
{
|
|
983
|
+
key: `${event.id}-${currentDate.toISOString()}`,
|
|
984
|
+
event,
|
|
985
|
+
className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
|
|
986
|
+
style: {
|
|
987
|
+
top: `${top}px`,
|
|
988
|
+
height: `${Math.max(height, 28)}px`,
|
|
989
|
+
left: `calc(${leftPercent}% + 4px)`,
|
|
990
|
+
width: `calc(${widthPercent}% - 8px)`,
|
|
991
|
+
paddingRight: count > 1 ? "2px" : "0"
|
|
992
|
+
}
|
|
993
|
+
},
|
|
994
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
995
|
+
ResizableEvent,
|
|
996
|
+
{
|
|
997
|
+
readonly,
|
|
998
|
+
event,
|
|
999
|
+
hourHeight,
|
|
1000
|
+
onResize: onEventResize,
|
|
1001
|
+
className: "h-full",
|
|
1002
|
+
style: { height: "100%" }
|
|
1003
|
+
},
|
|
1004
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1005
|
+
"div",
|
|
1006
|
+
{
|
|
1007
|
+
className: cn(
|
|
1008
|
+
"h-full rounded-lg border-[0.5px] shadow-sm overflow-hidden transition-all hover:shadow-lg hover:z-20 group",
|
|
1009
|
+
"glass backdrop-blur-sm",
|
|
1010
|
+
readonly ? "cursor-default" : "cursor-grab active:cursor-grabbing",
|
|
1011
|
+
!event.color && "bg-primary/10 border-primary/20",
|
|
1012
|
+
isShortEvent ? "px-2 flex items-center" : "px-3 py-2",
|
|
1013
|
+
count > 1 && "border-l-4"
|
|
1014
|
+
),
|
|
1015
|
+
style: {
|
|
1016
|
+
backgroundColor: event.color ? `${event.color}15` : void 0,
|
|
1017
|
+
borderColor: event.color ? `${event.color}30` : void 0,
|
|
1018
|
+
borderLeftColor: event.color || "var(--primary)",
|
|
1019
|
+
borderLeftWidth: count > 1 ? "4px" : "3px"
|
|
1020
|
+
},
|
|
1021
|
+
onClick: (e) => {
|
|
1022
|
+
e.stopPropagation();
|
|
1023
|
+
onEventClick?.(event);
|
|
1024
|
+
},
|
|
1025
|
+
title: count > 1 ? `${event.title} (${index + 1}/${count})` : void 0
|
|
1026
|
+
},
|
|
1027
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col h-full overflow-hidden w-full" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn(
|
|
1028
|
+
"font-semibold truncate leading-tight",
|
|
1029
|
+
isShortEvent ? "text-xs" : "text-sm",
|
|
1030
|
+
event.color ? "text-foreground" : "text-foreground/90"
|
|
1031
|
+
) }, event.title), !isShortEvent && /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, null, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-xs text-muted-foreground mt-0.5 font-medium" }, dateFns.format(zonedStart, eventTimeFormat, { locale }), " - ", dateFns.format(zonedEnd, eventTimeFormat, { locale })), event.description && height > 60 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-xs text-muted-foreground/80 mt-1 line-clamp-2" }, event.description)), count > 1 && !isShortEvent && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute top-1.5 right-1.5 bg-background/90 backdrop-blur-sm rounded-full w-5 h-5 flex items-center justify-center text-[10px] font-bold text-muted-foreground border border-border shadow-sm" }, count))
|
|
1032
|
+
)
|
|
1033
|
+
)
|
|
1034
|
+
);
|
|
1035
|
+
}), dateFns.isToday(currentDate) && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1036
|
+
"div",
|
|
1037
|
+
{
|
|
1038
|
+
className: "absolute left-0 right-0 z-20 pointer-events-none flex items-center",
|
|
1039
|
+
style: {
|
|
1040
|
+
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
|
|
1044
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute -left-1.5 w-3 h-3 bg-primary rounded-full shadow-lg shadow-primary/40 ring-2 ring-background animate-pulse" })
|
|
1045
|
+
)))));
|
|
1046
|
+
};
|
|
1047
|
+
var AgendaEmptyState = ({ onCreateEvent, translations }) => /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col items-center justify-center h-full min-h-[400px] py-12" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative mb-8" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "p-8 bg-gradient-to-br from-green-50 to-blue-50 dark:from-green-900/10 dark:to-blue-900/10 rounded-3xl border border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-12 h-12 rounded-2xl bg-green-100 dark:bg-green-900/30 flex items-center justify-center" }, /* @__PURE__ */ React15__namespace.default.createElement("svg", { className: "w-6 h-6 text-green-600 dark:text-green-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2 }, /* @__PURE__ */ React15__namespace.default.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-left" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "font-medium text-foreground" }, "All caught up!"), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-sm text-muted-foreground" }, "No events scheduled"))))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-center max-w-sm" }, /* @__PURE__ */ React15__namespace.default.createElement("h3", { className: "text-lg font-semibold text-foreground mb-2" }, translations?.noUpcoming || "Your schedule is clear"), /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-sm text-muted-foreground mb-6" }, "No upcoming events to show. Create a new event to start planning."), onCreateEvent && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1048
|
+
"button",
|
|
1049
|
+
{
|
|
1050
|
+
onClick: onCreateEvent,
|
|
1051
|
+
className: "inline-flex items-center gap-2 px-5 py-2.5 bg-primary text-primary-foreground rounded-xl font-medium text-sm shadow-lg shadow-primary/20 hover:shadow-xl hover:shadow-primary/30 hover:scale-105 active:scale-95 transition-all duration-200"
|
|
1052
|
+
},
|
|
1053
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Plus, { className: "w-4 h-4" }),
|
|
1054
|
+
translations?.createEvent || "Create Event"
|
|
1055
|
+
)));
|
|
1056
|
+
var formatDuration = (start, end) => {
|
|
1057
|
+
const minutes = dateFns.differenceInMinutes(end, start);
|
|
1058
|
+
if (minutes < 60) return `${minutes}m`;
|
|
1059
|
+
const hours = Math.floor(minutes / 60);
|
|
1060
|
+
const remainingMinutes = minutes % 60;
|
|
1061
|
+
if (remainingMinutes === 0) return `${hours}h`;
|
|
1062
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
1063
|
+
};
|
|
1064
|
+
var getDateLabel = (date) => {
|
|
1065
|
+
if (dateFns.isToday(date)) return "Today";
|
|
1066
|
+
if (dateFns.isTomorrow(date)) return "Tomorrow";
|
|
1067
|
+
return dateFns.format(date, "EEEE");
|
|
1068
|
+
};
|
|
1069
|
+
var AgendaView = ({
|
|
1070
|
+
currentDate,
|
|
1071
|
+
events,
|
|
1072
|
+
onEventClick,
|
|
1073
|
+
onCreateEvent
|
|
1074
|
+
}) => {
|
|
1075
|
+
const groupedEvents = React15.useMemo(() => {
|
|
1076
|
+
const startDate = dateFns.startOfDay(currentDate);
|
|
1077
|
+
const groups = [];
|
|
1078
|
+
const sortedEvents = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
1079
|
+
for (let i = 0; i < 30; i++) {
|
|
1080
|
+
const day = dateFns.addDays(startDate, i);
|
|
1081
|
+
const dayEvents = sortedEvents.filter((event) => dateFns.isSameDay(event.start, day));
|
|
1082
|
+
if (dayEvents.length > 0) {
|
|
1083
|
+
groups.push({ date: day, events: dayEvents });
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return groups;
|
|
1087
|
+
}, [currentDate, events]);
|
|
1088
|
+
const container = {
|
|
1089
|
+
hidden: { opacity: 0 },
|
|
1090
|
+
show: {
|
|
1091
|
+
opacity: 1,
|
|
1092
|
+
transition: {
|
|
1093
|
+
staggerChildren: 0.05
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
1097
|
+
const item = {
|
|
1098
|
+
hidden: { opacity: 0, x: -20 },
|
|
1099
|
+
show: { opacity: 1, x: 0 }
|
|
1100
|
+
};
|
|
1101
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col h-full bg-background overflow-y-auto" }, groupedEvents.length === 0 ? /* @__PURE__ */ React15__namespace.default.createElement(AgendaEmptyState, { onCreateEvent }) : /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1102
|
+
framerMotion.motion.div,
|
|
1103
|
+
{
|
|
1104
|
+
className: "max-w-3xl mx-auto w-full pb-10 px-4 md:px-6",
|
|
1105
|
+
variants: container,
|
|
1106
|
+
initial: "hidden",
|
|
1107
|
+
animate: "show"
|
|
1108
|
+
},
|
|
1109
|
+
groupedEvents.map((group, groupIndex) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1110
|
+
framerMotion.motion.div,
|
|
1111
|
+
{
|
|
1112
|
+
key: group.date.toISOString(),
|
|
1113
|
+
className: "relative",
|
|
1114
|
+
variants: item
|
|
1115
|
+
},
|
|
1116
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "sticky top-0 bg-background/95 backdrop-blur-md py-4 z-10 border-b border-border/50" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn(
|
|
1117
|
+
"flex flex-col items-center justify-center w-16 h-16 rounded-2xl transition-all",
|
|
1118
|
+
dateFns.isToday(group.date) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30" : "bg-muted/50 text-foreground"
|
|
1119
|
+
) }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-2xl font-bold leading-none" }, dateFns.format(group.date, "d")), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs font-medium uppercase tracking-wide opacity-80" }, dateFns.format(group.date, "MMM"))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: cn(
|
|
1120
|
+
"text-lg font-semibold",
|
|
1121
|
+
dateFns.isToday(group.date) && "text-primary"
|
|
1122
|
+
) }, getDateLabel(group.date)), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm text-muted-foreground" }, group.events.length, " event", group.events.length !== 1 ? "s" : "")))),
|
|
1123
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "py-4 space-y-3" }, group.events.map((event, eventIndex) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1124
|
+
framerMotion.motion.div,
|
|
1125
|
+
{
|
|
1126
|
+
key: event.id,
|
|
1127
|
+
onClick: () => onEventClick?.(event),
|
|
1128
|
+
className: cn(
|
|
1129
|
+
"group relative flex gap-4 p-4 rounded-2xl border border-border/40",
|
|
1130
|
+
"hover:border-primary/40 hover:shadow-lg hover:shadow-primary/5",
|
|
1131
|
+
"transition-all duration-200 cursor-pointer",
|
|
1132
|
+
"bg-gradient-to-br from-card via-card to-card/80"
|
|
1133
|
+
),
|
|
1134
|
+
whileHover: { scale: 1.01, y: -2 },
|
|
1135
|
+
transition: { duration: 0.2 }
|
|
1136
|
+
},
|
|
1137
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1138
|
+
"div",
|
|
1139
|
+
{
|
|
1140
|
+
className: "absolute left-0 top-3 bottom-3 w-1 rounded-full",
|
|
1141
|
+
style: { backgroundColor: event.color || "var(--primary)" }
|
|
1142
|
+
}
|
|
1143
|
+
),
|
|
1144
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col items-center min-w-[70px] pl-2" }, event.allDay ? /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs font-semibold text-muted-foreground bg-muted/80 px-2.5 py-1 rounded-full" }, "All Day")) : /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-base font-semibold text-foreground" }, dateFns.format(event.start, "h:mm")), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs text-muted-foreground uppercase" }, dateFns.format(event.start, "a")), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-px h-3 bg-border my-1" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs text-muted-foreground/70 font-medium" }, formatDuration(event.start, event.end)))),
|
|
1145
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 min-w-0 space-y-2" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-start justify-between gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement("h4", { className: "text-base font-semibold text-foreground group-hover:text-primary transition-colors line-clamp-1" }, event.title), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1146
|
+
"div",
|
|
1147
|
+
{
|
|
1148
|
+
className: "w-3 h-3 rounded-full shrink-0 mt-1.5",
|
|
1149
|
+
style: { backgroundColor: event.color || "var(--primary)" }
|
|
1150
|
+
}
|
|
1151
|
+
)), event.description && /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-sm text-muted-foreground line-clamp-2 leading-relaxed" }, event.description), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-wrap items-center gap-3 pt-1" }, !event.allDay && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Clock, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React15__namespace.default.createElement("span", null, dateFns.format(event.start, "h:mm a"), " - ", dateFns.format(event.end, "h:mm a"))), event.guests && event.guests.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Users, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React15__namespace.default.createElement("span", null, event.guests.length, " guest", event.guests.length !== 1 ? "s" : "")), event.attachments && event.attachments.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Paperclip, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React15__namespace.default.createElement("span", null, event.attachments.length)), event.reminders && event.reminders.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Bell, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React15__namespace.default.createElement("span", null, event.reminders.length)))),
|
|
1152
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute right-4 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity" }, /* @__PURE__ */ React15__namespace.default.createElement("svg", { className: "w-5 h-5 text-muted-foreground", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React15__namespace.default.createElement("path", { d: "M9 18l6-6-6-6" })))
|
|
1153
|
+
)))
|
|
1154
|
+
))
|
|
1155
|
+
));
|
|
1156
|
+
};
|
|
1157
|
+
var ResourceView = ({
|
|
1158
|
+
currentDate,
|
|
1159
|
+
events,
|
|
1160
|
+
resources,
|
|
1161
|
+
onEventClick,
|
|
1162
|
+
onTimeSlotClick,
|
|
1163
|
+
locale
|
|
1164
|
+
}) => {
|
|
1165
|
+
const containerRef = React15.useRef(null);
|
|
1166
|
+
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
1167
|
+
const hourWidth = 100;
|
|
1168
|
+
const timeFormat = locale?.code === "fr" ? "H:mm" : "h a";
|
|
1169
|
+
const getEventStyle = (event) => {
|
|
1170
|
+
const start = new Date(event.start);
|
|
1171
|
+
const end = new Date(event.end);
|
|
1172
|
+
const dayStart = dateFns.startOfDay(currentDate);
|
|
1173
|
+
const startMinutes = dateFns.differenceInMinutes(start, dayStart);
|
|
1174
|
+
const durationMinutes = dateFns.differenceInMinutes(end, start);
|
|
1175
|
+
const left = startMinutes / 60 * hourWidth;
|
|
1176
|
+
const width = durationMinutes / 60 * hourWidth;
|
|
1177
|
+
return {
|
|
1178
|
+
left: `${left}px`,
|
|
1179
|
+
width: `${Math.max(width, 4)}px`
|
|
1180
|
+
// Min width for visibility
|
|
1181
|
+
};
|
|
1182
|
+
};
|
|
1183
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col h-full bg-background overflow-hidden" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex border-b border-border bg-muted/20" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-48 shrink-0 border-r border-border p-4 font-semibold text-sm bg-background sticky left-0 z-20" }, "Resources"), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex overflow-hidden" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex relative", style: { width: hours.length * hourWidth } }, hours.map((hour) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1184
|
+
"div",
|
|
1185
|
+
{
|
|
1186
|
+
key: hour,
|
|
1187
|
+
className: "border-r border-border/50 text-xs text-muted-foreground p-2 font-medium shrink-0",
|
|
1188
|
+
style: { width: hourWidth }
|
|
1189
|
+
},
|
|
1190
|
+
dateFns.format((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale })
|
|
1191
|
+
))))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 overflow-auto relative", ref: containerRef }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "min-w-fit" }, resources.map((resource) => {
|
|
1192
|
+
const resourceEvents = events.filter(
|
|
1193
|
+
(e) => e.resourceId === resource.id && dateFns.isSameDay(new Date(e.start), currentDate)
|
|
1194
|
+
);
|
|
1195
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { key: resource.id, className: "flex border-b border-border min-h-[100px]" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-48 shrink-0 border-r border-border p-4 bg-background sticky left-0 z-10 flex items-center gap-3" }, resource.avatar ? /* @__PURE__ */ React15__namespace.default.createElement("img", { src: resource.avatar, alt: resource.label, className: "w-8 h-8 rounded-full object-cover" }) : /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-bold text-primary" }, resource.label.substring(0, 2).toUpperCase()), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium" }, resource.label), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs text-muted-foreground" }, "ID: ", resource.id))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative flex", style: { width: hours.length * hourWidth } }, hours.map((hour) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1196
|
+
"div",
|
|
1197
|
+
{
|
|
1198
|
+
key: hour,
|
|
1199
|
+
className: "flex h-full shrink-0",
|
|
1200
|
+
style: { width: hourWidth }
|
|
1201
|
+
},
|
|
1202
|
+
[0, 15, 30, 45].map((minute) => {
|
|
1203
|
+
const slotDate = new Date(currentDate);
|
|
1204
|
+
slotDate.setHours(hour, minute, 0, 0);
|
|
1205
|
+
const slotId = `${resource.id}-${slotDate.toISOString()}`;
|
|
1206
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1207
|
+
DroppableCell,
|
|
1208
|
+
{
|
|
1209
|
+
key: minute,
|
|
1210
|
+
id: slotId,
|
|
1211
|
+
date: slotDate,
|
|
1212
|
+
resourceId: resource.id,
|
|
1213
|
+
className: "h-full flex-1 border-r border-border/10 last:border-border/30 hover:bg-accent/10 transition-colors",
|
|
1214
|
+
onClick: () => {
|
|
1215
|
+
onTimeSlotClick?.(slotDate, resource.id);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
);
|
|
1219
|
+
})
|
|
1220
|
+
)), resourceEvents.map((event) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1221
|
+
DraggableEvent,
|
|
1222
|
+
{
|
|
1223
|
+
key: event.id,
|
|
1224
|
+
event,
|
|
1225
|
+
onClick: (e) => {
|
|
1226
|
+
e.stopPropagation();
|
|
1227
|
+
onEventClick?.(event);
|
|
1228
|
+
},
|
|
1229
|
+
className: "absolute top-2 bottom-2 rounded-md px-2 py-1 text-xs font-medium border shadow-sm cursor-pointer overflow-hidden hover:brightness-95 transition-all z-10",
|
|
1230
|
+
style: {
|
|
1231
|
+
...getEventStyle(event),
|
|
1232
|
+
backgroundColor: event.color || resource.color || "var(--primary)",
|
|
1233
|
+
borderColor: "rgba(0,0,0,0.1)",
|
|
1234
|
+
color: "#fff"
|
|
1235
|
+
}
|
|
1236
|
+
},
|
|
1237
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "truncate" }, event.title)
|
|
1238
|
+
))));
|
|
1239
|
+
}))));
|
|
1240
|
+
};
|
|
1241
|
+
var Modal = ({
|
|
1242
|
+
isOpen,
|
|
1243
|
+
onClose,
|
|
1244
|
+
title,
|
|
1245
|
+
children,
|
|
1246
|
+
className,
|
|
1247
|
+
hideHeader = false
|
|
1248
|
+
}) => {
|
|
1249
|
+
const overlayRef = React15.useRef(null);
|
|
1250
|
+
React15.useEffect(() => {
|
|
1251
|
+
const handleEscape = (e) => {
|
|
1252
|
+
if (e.key === "Escape") {
|
|
1253
|
+
onClose();
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
if (isOpen) {
|
|
1257
|
+
document.addEventListener("keydown", handleEscape);
|
|
1258
|
+
}
|
|
1259
|
+
return () => {
|
|
1260
|
+
document.removeEventListener("keydown", handleEscape);
|
|
1261
|
+
};
|
|
1262
|
+
}, [isOpen, onClose]);
|
|
1263
|
+
if (!isOpen) return null;
|
|
1264
|
+
return /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute inset-0 z-50 flex items-center justify-center p-4 bg-black/40 backdrop-blur-md animate-in fade-in duration-300" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1265
|
+
"div",
|
|
1266
|
+
{
|
|
1267
|
+
ref: overlayRef,
|
|
1268
|
+
className: "absolute inset-0",
|
|
1269
|
+
onClick: onClose
|
|
1270
|
+
}
|
|
1271
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1272
|
+
"div",
|
|
1273
|
+
{
|
|
1274
|
+
className: cn(
|
|
1275
|
+
"relative bg-background rounded-2xl shadow-2xl w-full max-w-md flex flex-col max-h-[70vh] sm:max-h-[80vh] animate-in zoom-in-95 slide-in-from-bottom-4 duration-300 border border-border/50",
|
|
1276
|
+
className
|
|
1277
|
+
)
|
|
1278
|
+
},
|
|
1279
|
+
!hideHeader && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-between px-6 py-4 border-b border-border/50 shrink-0 bg-gradient-to-r from-muted/20 to-background" }, /* @__PURE__ */ React15__namespace.default.createElement("h3", { className: "text-lg font-semibold text-foreground" }, title), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1280
|
+
Button,
|
|
1281
|
+
{
|
|
1282
|
+
variant: "ghost",
|
|
1283
|
+
size: "sm",
|
|
1284
|
+
onClick: onClose,
|
|
1285
|
+
className: "h-9 w-9 p-0 rounded-xl hover:bg-accent/80 transition-all duration-200"
|
|
1286
|
+
},
|
|
1287
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.X, { className: "h-4 w-4" }),
|
|
1288
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "sr-only" }, "Close")
|
|
1289
|
+
)),
|
|
1290
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn("overflow-y-auto flex-1", !hideHeader && "p-6") }, children)
|
|
1291
|
+
));
|
|
1292
|
+
};
|
|
1293
|
+
var REMINDER_OPTIONS = [
|
|
1294
|
+
{ value: 0, label: "At time of event" },
|
|
1295
|
+
{ value: 5, label: "5 minutes before" },
|
|
1296
|
+
{ value: 10, label: "10 minutes before" },
|
|
1297
|
+
{ value: 15, label: "15 minutes before" },
|
|
1298
|
+
{ value: 30, label: "30 minutes before" },
|
|
1299
|
+
{ value: 60, label: "1 hour before" },
|
|
1300
|
+
{ value: 120, label: "2 hours before" },
|
|
1301
|
+
{ value: 1440, label: "1 day before" },
|
|
1302
|
+
{ value: 2880, label: "2 days before" },
|
|
1303
|
+
{ value: 10080, label: "1 week before" }
|
|
1304
|
+
];
|
|
1305
|
+
var COLOR_PALETTE = [
|
|
1306
|
+
"#3b82f6",
|
|
1307
|
+
// Blue
|
|
1308
|
+
"#ef4444",
|
|
1309
|
+
// Red
|
|
1310
|
+
"#10b981",
|
|
1311
|
+
// Emerald
|
|
1312
|
+
"#f59e0b",
|
|
1313
|
+
// Amber
|
|
1314
|
+
"#8b5cf6",
|
|
1315
|
+
// Violet
|
|
1316
|
+
"#ec4899",
|
|
1317
|
+
// Pink
|
|
1318
|
+
"#06b6d4",
|
|
1319
|
+
// Cyan
|
|
1320
|
+
"#84cc16",
|
|
1321
|
+
// Lime
|
|
1322
|
+
"#f97316",
|
|
1323
|
+
// Orange
|
|
1324
|
+
"#6366f1"
|
|
1325
|
+
// Indigo
|
|
1326
|
+
];
|
|
1327
|
+
var EventModal = ({
|
|
1328
|
+
isOpen,
|
|
1329
|
+
onClose,
|
|
1330
|
+
event,
|
|
1331
|
+
initialDate,
|
|
1332
|
+
onSave,
|
|
1333
|
+
onDelete,
|
|
1334
|
+
calendars,
|
|
1335
|
+
eventTypes,
|
|
1336
|
+
translations
|
|
1337
|
+
}) => {
|
|
1338
|
+
const [mode, setMode] = React15.useState("create");
|
|
1339
|
+
const [activeTab, setActiveTab] = React15.useState("details");
|
|
1340
|
+
const [formData, setFormData] = React15.useState({
|
|
1341
|
+
title: "",
|
|
1342
|
+
description: "",
|
|
1343
|
+
location: "",
|
|
1344
|
+
start: /* @__PURE__ */ new Date(),
|
|
1345
|
+
end: /* @__PURE__ */ new Date(),
|
|
1346
|
+
allDay: false,
|
|
1347
|
+
color: "#3b82f6",
|
|
1348
|
+
calendarId: calendars?.[0]?.id,
|
|
1349
|
+
type: void 0,
|
|
1350
|
+
recurrence: void 0,
|
|
1351
|
+
attachments: [],
|
|
1352
|
+
reminders: [{ id: "1", type: "notification", time: 30, label: "30 minutes before" }]
|
|
1353
|
+
});
|
|
1354
|
+
const [isCalendarDropdownOpen, setIsCalendarDropdownOpen] = React15.useState(false);
|
|
1355
|
+
const [isColorPickerOpen, setIsColorPickerOpen] = React15.useState(false);
|
|
1356
|
+
const [isReminderDropdownOpen, setIsReminderDropdownOpen] = React15.useState(false);
|
|
1357
|
+
const [isGuestsDropdownOpen, setIsGuestsDropdownOpen] = React15.useState(false);
|
|
1358
|
+
const fakeGuests = [
|
|
1359
|
+
"alice@example.com",
|
|
1360
|
+
"bob@example.com",
|
|
1361
|
+
"charlie@example.com",
|
|
1362
|
+
"diana@example.com",
|
|
1363
|
+
"evan@example.com"
|
|
1364
|
+
];
|
|
1365
|
+
React15.useRef(null);
|
|
1366
|
+
const [selectedGuests, setSelectedGuests] = React15.useState([]);
|
|
1367
|
+
React15.useEffect(() => {
|
|
1368
|
+
if (isOpen) {
|
|
1369
|
+
if (event) {
|
|
1370
|
+
setMode("view");
|
|
1371
|
+
setFormData({
|
|
1372
|
+
...event,
|
|
1373
|
+
start: new Date(event.start),
|
|
1374
|
+
end: new Date(event.end),
|
|
1375
|
+
attachments: event.attachments || [],
|
|
1376
|
+
reminders: event.reminders || [{ id: "1", type: "notification", time: 30, label: "30 minutes before" }]
|
|
1377
|
+
});
|
|
1378
|
+
setSelectedGuests(event.guests || []);
|
|
1379
|
+
} else {
|
|
1380
|
+
setMode("create");
|
|
1381
|
+
const start = initialDate || /* @__PURE__ */ new Date();
|
|
1382
|
+
const end = new Date(start);
|
|
1383
|
+
end.setHours(start.getHours() + 1);
|
|
1384
|
+
setFormData({
|
|
1385
|
+
title: "",
|
|
1386
|
+
description: "",
|
|
1387
|
+
location: "",
|
|
1388
|
+
start,
|
|
1389
|
+
end,
|
|
1390
|
+
allDay: false,
|
|
1391
|
+
color: "#3b82f6",
|
|
1392
|
+
calendarId: calendars?.[0]?.id,
|
|
1393
|
+
attachments: [],
|
|
1394
|
+
reminders: [{ id: "1", type: "notification", time: 30, label: "30 minutes before" }]
|
|
1395
|
+
});
|
|
1396
|
+
setSelectedGuests([]);
|
|
1397
|
+
setActiveTab("details");
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}, [isOpen, event, initialDate, calendars]);
|
|
1401
|
+
const toggleGuest = (email) => {
|
|
1402
|
+
setSelectedGuests((prev) => {
|
|
1403
|
+
const newGuests = prev.includes(email) ? prev.filter((e) => e !== email) : [...prev, email];
|
|
1404
|
+
setFormData((curr) => ({ ...curr, guests: newGuests }));
|
|
1405
|
+
return newGuests;
|
|
1406
|
+
});
|
|
1407
|
+
};
|
|
1408
|
+
const handleSubmit = (e) => {
|
|
1409
|
+
e.preventDefault();
|
|
1410
|
+
let eventColor = formData.color;
|
|
1411
|
+
if (formData.calendarId && calendars) {
|
|
1412
|
+
const selectedCal = calendars.find((c) => c.id === formData.calendarId);
|
|
1413
|
+
if (selectedCal?.color) {
|
|
1414
|
+
eventColor = selectedCal.color;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
onSave({
|
|
1418
|
+
...formData,
|
|
1419
|
+
color: eventColor,
|
|
1420
|
+
id: event?.id
|
|
1421
|
+
});
|
|
1422
|
+
onClose();
|
|
1423
|
+
};
|
|
1424
|
+
const handleDelete = () => {
|
|
1425
|
+
if (event?.id && onDelete) {
|
|
1426
|
+
onDelete(event.id);
|
|
1427
|
+
onClose();
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
const formatDateForInput = (date) => {
|
|
1431
|
+
if (!date) return "";
|
|
1432
|
+
return dateFns.format(date, "yyyy-MM-dd'T'HH:mm");
|
|
1433
|
+
};
|
|
1434
|
+
const handleDateChange = (field, value) => {
|
|
1435
|
+
const date = new Date(value);
|
|
1436
|
+
if (isNaN(date.getTime())) return;
|
|
1437
|
+
if (field === "start") {
|
|
1438
|
+
const duration = formData.end.getTime() - formData.start.getTime();
|
|
1439
|
+
setFormData((prev) => ({
|
|
1440
|
+
...prev,
|
|
1441
|
+
start: date,
|
|
1442
|
+
end: new Date(date.getTime() + duration)
|
|
1443
|
+
}));
|
|
1444
|
+
} else {
|
|
1445
|
+
if (date < formData.start) {
|
|
1446
|
+
setFormData((prev) => ({ ...prev, start: date, end: date }));
|
|
1447
|
+
} else {
|
|
1448
|
+
setFormData((prev) => ({ ...prev, end: date }));
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
const addReminder = (minutes, label) => {
|
|
1453
|
+
const newReminder = {
|
|
1454
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
1455
|
+
type: "notification",
|
|
1456
|
+
time: minutes,
|
|
1457
|
+
label
|
|
1458
|
+
};
|
|
1459
|
+
setFormData((prev) => ({
|
|
1460
|
+
...prev,
|
|
1461
|
+
reminders: [...prev.reminders || [], newReminder]
|
|
1462
|
+
}));
|
|
1463
|
+
setIsReminderDropdownOpen(false);
|
|
1464
|
+
};
|
|
1465
|
+
const removeReminder = (id) => {
|
|
1466
|
+
setFormData((prev) => ({
|
|
1467
|
+
...prev,
|
|
1468
|
+
reminders: prev.reminders?.filter((r) => r.id !== id)
|
|
1469
|
+
}));
|
|
1470
|
+
};
|
|
1471
|
+
const handleDownload = (attachment) => {
|
|
1472
|
+
const element = document.createElement("a");
|
|
1473
|
+
const file = new Blob(["Dummy content for " + attachment.name], { type: "text/plain" });
|
|
1474
|
+
element.href = URL.createObjectURL(file);
|
|
1475
|
+
element.download = attachment.name;
|
|
1476
|
+
document.body.appendChild(element);
|
|
1477
|
+
element.click();
|
|
1478
|
+
document.body.removeChild(element);
|
|
1479
|
+
};
|
|
1480
|
+
const getDurationText = () => {
|
|
1481
|
+
if (!formData.start || !formData.end) return "";
|
|
1482
|
+
const mins = dateFns.differenceInMinutes(formData.end, formData.start);
|
|
1483
|
+
if (mins < 60) return `${mins} min`;
|
|
1484
|
+
const hours = Math.floor(mins / 60);
|
|
1485
|
+
const remainingMins = mins % 60;
|
|
1486
|
+
if (remainingMins === 0) return `${hours} hr`;
|
|
1487
|
+
return `${hours} hr ${remainingMins} min`;
|
|
1488
|
+
};
|
|
1489
|
+
const renderViewMode = () => /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-col bg-background overflow-hidden w-full max-h-[90vh]" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1490
|
+
"div",
|
|
1491
|
+
{
|
|
1492
|
+
className: "relative px-6 pt-6 pb-8",
|
|
1493
|
+
style: {
|
|
1494
|
+
background: `linear-gradient(135deg, ${event?.color || "#3b82f6"}15 0%, transparent 100%)`
|
|
1495
|
+
}
|
|
1496
|
+
},
|
|
1497
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-between mb-6" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1498
|
+
"div",
|
|
1499
|
+
{
|
|
1500
|
+
className: "w-3 h-3 rounded-full",
|
|
1501
|
+
style: { backgroundColor: event?.color || "#3b82f6" }
|
|
1502
|
+
}
|
|
1503
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider" }, calendars?.find((c) => c.id === event?.calendarId)?.label || "Event")), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-1" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1504
|
+
"button",
|
|
1505
|
+
{
|
|
1506
|
+
onClick: () => setMode("edit"),
|
|
1507
|
+
className: "p-2 hover:bg-background/80 rounded-lg transition-all text-muted-foreground hover:text-foreground",
|
|
1508
|
+
title: "Edit"
|
|
1509
|
+
},
|
|
1510
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Edit2, { className: "w-4 h-4" })
|
|
1511
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1512
|
+
"button",
|
|
1513
|
+
{
|
|
1514
|
+
onClick: handleDelete,
|
|
1515
|
+
className: "p-2 hover:bg-destructive/10 rounded-lg transition-all text-muted-foreground hover:text-destructive",
|
|
1516
|
+
title: "Delete"
|
|
1517
|
+
},
|
|
1518
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Trash2, { className: "w-4 h-4" })
|
|
1519
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1520
|
+
"button",
|
|
1521
|
+
{
|
|
1522
|
+
onClick: onClose,
|
|
1523
|
+
className: "p-2 hover:bg-background/80 rounded-lg transition-all text-muted-foreground hover:text-foreground",
|
|
1524
|
+
title: "Close"
|
|
1525
|
+
},
|
|
1526
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.X, { className: "w-4 h-4" })
|
|
1527
|
+
))),
|
|
1528
|
+
/* @__PURE__ */ React15__namespace.default.createElement("h1", { className: "text-2xl font-bold text-foreground mb-3 leading-tight" }, event?.title || "(No title)"),
|
|
1529
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-3 text-sm" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Calendar, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "font-medium" }, event?.start && dateFns.format(new Date(event.start), "EEE, MMM d"))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Clock, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "font-medium" }, event?.start && dateFns.format(new Date(event.start), "h:mm a"), " \u2013 ", event?.end && dateFns.format(new Date(event.end), "h:mm a"))))
|
|
1530
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "px-6 pb-6 space-y-4 overflow-y-auto flex-1" }, event?.location && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1531
|
+
framerMotion.motion.div,
|
|
1532
|
+
{
|
|
1533
|
+
initial: { opacity: 0, y: 10 },
|
|
1534
|
+
animate: { opacity: 1, y: 0 },
|
|
1535
|
+
className: "flex items-start gap-3 p-4 rounded-2xl bg-muted/30 border border-border/30 group hover:bg-muted/50 transition-all cursor-pointer"
|
|
1536
|
+
},
|
|
1537
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "p-2 bg-primary/10 rounded-xl" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.MapPin, { className: "w-5 h-5 text-primary" })),
|
|
1538
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-sm font-medium text-foreground" }, event.location), /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-xs text-muted-foreground mt-0.5" }, "Click to open in maps")),
|
|
1539
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ExternalLink, { className: "w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" })
|
|
1540
|
+
), event?.guests && event.guests.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1541
|
+
framerMotion.motion.div,
|
|
1542
|
+
{
|
|
1543
|
+
initial: { opacity: 0, y: 10 },
|
|
1544
|
+
animate: { opacity: 1, y: 0 },
|
|
1545
|
+
transition: { delay: 0.05 },
|
|
1546
|
+
className: "space-y-3"
|
|
1547
|
+
},
|
|
1548
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Users, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium text-foreground" }, event.guests.length, " ", event.guests.length > 1 ? "Guests" : "Guest"))),
|
|
1549
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-wrap gap-2" }, event.guests.map((email, index) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1550
|
+
"div",
|
|
1551
|
+
{
|
|
1552
|
+
key: index,
|
|
1553
|
+
className: "flex items-center gap-2 px-3 py-2 rounded-xl bg-muted/30 border border-border/30 hover:bg-muted/50 transition-all cursor-pointer group"
|
|
1554
|
+
},
|
|
1555
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1556
|
+
"div",
|
|
1557
|
+
{
|
|
1558
|
+
className: "w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold text-white",
|
|
1559
|
+
style: { backgroundColor: `hsl(${index * 60}, 70%, 50%)` }
|
|
1560
|
+
},
|
|
1561
|
+
email[0].toUpperCase()
|
|
1562
|
+
),
|
|
1563
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-sm font-medium text-foreground" }, email.split("@")[0]), /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-xs text-muted-foreground truncate" }, email))
|
|
1564
|
+
)))
|
|
1565
|
+
), event?.reminders && event.reminders.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1566
|
+
framerMotion.motion.div,
|
|
1567
|
+
{
|
|
1568
|
+
initial: { opacity: 0, y: 10 },
|
|
1569
|
+
animate: { opacity: 1, y: 0 },
|
|
1570
|
+
transition: { delay: 0.1 },
|
|
1571
|
+
className: "flex items-center gap-3"
|
|
1572
|
+
},
|
|
1573
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "p-2 bg-amber-500/10 rounded-xl" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Bell, { className: "w-4 h-4 text-amber-500" })),
|
|
1574
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-wrap gap-2" }, event.reminders.map((reminder) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1575
|
+
"span",
|
|
1576
|
+
{
|
|
1577
|
+
key: reminder.id,
|
|
1578
|
+
className: "text-xs font-medium px-2.5 py-1 rounded-full bg-amber-500/10 text-amber-600 dark:text-amber-400"
|
|
1579
|
+
},
|
|
1580
|
+
reminder.label || `${reminder.time} min before`
|
|
1581
|
+
)))
|
|
1582
|
+
), event?.description && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1583
|
+
framerMotion.motion.div,
|
|
1584
|
+
{
|
|
1585
|
+
initial: { opacity: 0, y: 10 },
|
|
1586
|
+
animate: { opacity: 1, y: 0 },
|
|
1587
|
+
transition: { delay: 0.15 },
|
|
1588
|
+
className: "space-y-2"
|
|
1589
|
+
},
|
|
1590
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.AlignLeft, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium text-foreground" }, "Notes")),
|
|
1591
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-sm text-foreground/80 whitespace-pre-wrap leading-relaxed pl-6 p-3 rounded-xl bg-muted/20 border border-border/30" }, event.description)
|
|
1592
|
+
), event?.attachments && event.attachments.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1593
|
+
framerMotion.motion.div,
|
|
1594
|
+
{
|
|
1595
|
+
initial: { opacity: 0, y: 10 },
|
|
1596
|
+
animate: { opacity: 1, y: 0 },
|
|
1597
|
+
transition: { delay: 0.2 },
|
|
1598
|
+
className: "space-y-2"
|
|
1599
|
+
},
|
|
1600
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Paperclip, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium text-foreground" }, "Attachments")),
|
|
1601
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "grid gap-2" }, event.attachments.map((att) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1602
|
+
"div",
|
|
1603
|
+
{
|
|
1604
|
+
key: att.id,
|
|
1605
|
+
onClick: () => handleDownload(att),
|
|
1606
|
+
className: "flex items-center gap-3 p-3 rounded-xl border border-border/30 bg-muted/20 hover:bg-muted/40 transition-all cursor-pointer group"
|
|
1607
|
+
},
|
|
1608
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "p-2 bg-primary/10 rounded-lg" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.File, { className: "w-4 h-4 text-primary" })),
|
|
1609
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-sm font-medium text-foreground truncate" }, att.name), /* @__PURE__ */ React15__namespace.default.createElement("p", { className: "text-xs text-muted-foreground" }, att.size)),
|
|
1610
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Download, { className: "w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" })
|
|
1611
|
+
)))
|
|
1612
|
+
)));
|
|
1613
|
+
const renderEditMode = () => /* @__PURE__ */ React15__namespace.default.createElement("form", { onSubmit: handleSubmit, className: "flex flex-col bg-background overflow-hidden w-full max-h-[85vh]" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-between px-5 py-4 border-b border-border/50 shrink-0" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1614
|
+
"button",
|
|
1615
|
+
{
|
|
1616
|
+
type: "button",
|
|
1617
|
+
onClick: () => setIsColorPickerOpen(!isColorPickerOpen),
|
|
1618
|
+
className: "relative p-1"
|
|
1619
|
+
},
|
|
1620
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1621
|
+
"div",
|
|
1622
|
+
{
|
|
1623
|
+
className: "w-5 h-5 rounded-full cursor-pointer hover:scale-110 transition-transform",
|
|
1624
|
+
style: {
|
|
1625
|
+
backgroundColor: formData.color,
|
|
1626
|
+
boxShadow: `0 0 0 2px var(--background), 0 0 0 4px ${formData.color}60`
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
),
|
|
1630
|
+
isColorPickerOpen && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute top-full left-0 mt-2 p-2 bg-background border border-border rounded-xl shadow-xl z-50 flex flex-wrap gap-1.5 w-[180px]" }, COLOR_PALETTE.map((color) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1631
|
+
"button",
|
|
1632
|
+
{
|
|
1633
|
+
key: color,
|
|
1634
|
+
type: "button",
|
|
1635
|
+
onClick: () => {
|
|
1636
|
+
setFormData((prev) => ({ ...prev, color }));
|
|
1637
|
+
setIsColorPickerOpen(false);
|
|
1638
|
+
},
|
|
1639
|
+
className: cn(
|
|
1640
|
+
"w-7 h-7 rounded-full transition-all hover:scale-110",
|
|
1641
|
+
formData.color === color && "ring-2 ring-offset-2 ring-offset-background ring-current"
|
|
1642
|
+
),
|
|
1643
|
+
style: { backgroundColor: color, color }
|
|
1644
|
+
}
|
|
1645
|
+
)))
|
|
1646
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("h2", { className: "text-lg font-semibold text-foreground" }, mode === "edit" ? "Edit Event" : "New Event")), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1647
|
+
"button",
|
|
1648
|
+
{
|
|
1649
|
+
type: "button",
|
|
1650
|
+
onClick: onClose,
|
|
1651
|
+
className: "p-2 hover:bg-accent rounded-xl transition-all text-muted-foreground hover:text-foreground"
|
|
1652
|
+
},
|
|
1653
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.X, { className: "w-4 h-4" })
|
|
1654
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 overflow-y-auto" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "px-5 py-4 space-y-5" }, /* @__PURE__ */ React15__namespace.default.createElement("div", null, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1655
|
+
"input",
|
|
1656
|
+
{
|
|
1657
|
+
type: "text",
|
|
1658
|
+
required: true,
|
|
1659
|
+
autoFocus: true,
|
|
1660
|
+
className: "w-full text-xl font-semibold border-0 border-b-2 border-transparent focus:border-primary bg-transparent px-0 py-2 placeholder-muted-foreground/50 transition-all text-foreground focus:outline-none",
|
|
1661
|
+
value: formData.title,
|
|
1662
|
+
onChange: (e) => setFormData({ ...formData, title: e.target.value }),
|
|
1663
|
+
placeholder: "Add title"
|
|
1664
|
+
}
|
|
1665
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-3 p-4 rounded-2xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "p-2 bg-primary/10 rounded-xl shrink-0" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Clock, { className: "w-5 h-5 text-primary" })), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 grid grid-cols-2 gap-3" }, /* @__PURE__ */ React15__namespace.default.createElement("div", null, /* @__PURE__ */ React15__namespace.default.createElement("label", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1 block" }, "Start"), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1666
|
+
"input",
|
|
1667
|
+
{
|
|
1668
|
+
type: "datetime-local",
|
|
1669
|
+
className: "w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm font-medium text-foreground focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all",
|
|
1670
|
+
value: formatDateForInput(formData.start),
|
|
1671
|
+
onChange: (e) => handleDateChange("start", e.target.value)
|
|
1672
|
+
}
|
|
1673
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", null, /* @__PURE__ */ React15__namespace.default.createElement("label", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1 block" }, "End"), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1674
|
+
"input",
|
|
1675
|
+
{
|
|
1676
|
+
type: "datetime-local",
|
|
1677
|
+
className: "w-full bg-background border border-border/50 rounded-xl px-3 py-2 text-sm font-medium text-foreground focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all",
|
|
1678
|
+
value: formatDateForInput(formData.end),
|
|
1679
|
+
onChange: (e) => handleDateChange("end", e.target.value)
|
|
1680
|
+
}
|
|
1681
|
+
)))), getDurationText() && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex justify-center" }, /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs font-medium px-3 py-1 rounded-full bg-primary/10 text-primary" }, getDurationText())), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Bell, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium text-foreground" }, "Reminders")), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1682
|
+
"button",
|
|
1683
|
+
{
|
|
1684
|
+
type: "button",
|
|
1685
|
+
onClick: () => setIsReminderDropdownOpen(!isReminderDropdownOpen),
|
|
1686
|
+
className: "flex items-center gap-1 text-xs font-medium text-primary hover:text-primary/80 transition-colors"
|
|
1687
|
+
},
|
|
1688
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Plus, { className: "w-3.5 h-3.5" }),
|
|
1689
|
+
"Add"
|
|
1690
|
+
), isReminderDropdownOpen && /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, null, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsReminderDropdownOpen(false) }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute right-0 top-full mt-1 w-48 bg-background border border-border rounded-xl shadow-xl z-50 py-1 max-h-60 overflow-y-auto" }, REMINDER_OPTIONS.map((option) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1691
|
+
"button",
|
|
1692
|
+
{
|
|
1693
|
+
key: option.value,
|
|
1694
|
+
type: "button",
|
|
1695
|
+
onClick: () => addReminder(option.value, option.label),
|
|
1696
|
+
className: "w-full px-3 py-2 text-left text-sm hover:bg-accent transition-colors"
|
|
1697
|
+
},
|
|
1698
|
+
option.label
|
|
1699
|
+
)))))), formData.reminders && formData.reminders.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-wrap gap-2" }, formData.reminders.map((reminder) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1700
|
+
"div",
|
|
1701
|
+
{
|
|
1702
|
+
key: reminder.id,
|
|
1703
|
+
className: "flex items-center gap-2 px-3 py-1.5 rounded-full bg-amber-500/10 border border-amber-500/20"
|
|
1704
|
+
},
|
|
1705
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.BellRing, { className: "w-3.5 h-3.5 text-amber-500" }),
|
|
1706
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400" }, reminder.label),
|
|
1707
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1708
|
+
"button",
|
|
1709
|
+
{
|
|
1710
|
+
type: "button",
|
|
1711
|
+
onClick: () => removeReminder(reminder.id),
|
|
1712
|
+
className: "p-0.5 hover:bg-amber-500/20 rounded-full transition-colors"
|
|
1713
|
+
},
|
|
1714
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.X, { className: "w-3 h-3 text-amber-500" })
|
|
1715
|
+
)
|
|
1716
|
+
)))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Users, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium text-foreground" }, "Guests")), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1717
|
+
"button",
|
|
1718
|
+
{
|
|
1719
|
+
type: "button",
|
|
1720
|
+
onClick: () => setIsGuestsDropdownOpen(!isGuestsDropdownOpen),
|
|
1721
|
+
className: "w-full flex items-center justify-between px-4 py-3 bg-muted/20 border border-border/30 rounded-xl text-sm text-left hover:bg-muted/30 transition-all"
|
|
1722
|
+
},
|
|
1723
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: selectedGuests.length > 0 ? "text-foreground font-medium" : "text-muted-foreground" }, selectedGuests.length > 0 ? `${selectedGuests.length} guest${selectedGuests.length > 1 ? "s" : ""} added` : "Add guests"),
|
|
1724
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronDown, { className: cn("w-4 h-4 text-muted-foreground transition-transform", isGuestsDropdownOpen && "rotate-180") })
|
|
1725
|
+
), isGuestsDropdownOpen && /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, null, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsGuestsDropdownOpen(false) }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute z-50 w-full mt-2 bg-background border border-border rounded-xl shadow-xl max-h-48 overflow-y-auto" }, fakeGuests.map((email) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1726
|
+
"div",
|
|
1727
|
+
{
|
|
1728
|
+
key: email,
|
|
1729
|
+
className: "px-4 py-3 hover:bg-accent cursor-pointer flex items-center gap-3 transition-colors",
|
|
1730
|
+
onClick: () => toggleGuest(email)
|
|
1731
|
+
},
|
|
1732
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn(
|
|
1733
|
+
"w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all",
|
|
1734
|
+
selectedGuests.includes(email) ? "bg-primary border-primary" : "border-border"
|
|
1735
|
+
) }, selectedGuests.includes(email) && /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Check, { className: "w-3 h-3 text-primary-foreground" })),
|
|
1736
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1737
|
+
"div",
|
|
1738
|
+
{
|
|
1739
|
+
className: "w-7 h-7 rounded-full flex items-center justify-center text-xs font-semibold text-white",
|
|
1740
|
+
style: { backgroundColor: `hsl(${fakeGuests.indexOf(email) * 60}, 70%, 50%)` }
|
|
1741
|
+
},
|
|
1742
|
+
email[0].toUpperCase()
|
|
1743
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm" }, email))
|
|
1744
|
+
))))), selectedGuests.length > 0 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-wrap gap-2 mt-2" }, selectedGuests.map((email) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1745
|
+
"div",
|
|
1746
|
+
{
|
|
1747
|
+
key: email,
|
|
1748
|
+
className: "flex items-center gap-2 pl-1 pr-2 py-1 bg-muted/30 border border-border/30 rounded-full"
|
|
1749
|
+
},
|
|
1750
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1751
|
+
"div",
|
|
1752
|
+
{
|
|
1753
|
+
className: "w-6 h-6 rounded-full flex items-center justify-center text-xs font-semibold text-white",
|
|
1754
|
+
style: { backgroundColor: `hsl(${fakeGuests.indexOf(email) * 60}, 70%, 50%)` }
|
|
1755
|
+
},
|
|
1756
|
+
email[0].toUpperCase()
|
|
1757
|
+
),
|
|
1758
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-xs font-medium text-foreground" }, email.split("@")[0]),
|
|
1759
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1760
|
+
"button",
|
|
1761
|
+
{
|
|
1762
|
+
type: "button",
|
|
1763
|
+
onClick: () => toggleGuest(email),
|
|
1764
|
+
className: "p-0.5 hover:bg-destructive/10 rounded-full transition-colors"
|
|
1765
|
+
},
|
|
1766
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.X, { className: "w-3 h-3 text-muted-foreground" })
|
|
1767
|
+
)
|
|
1768
|
+
)))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-3 p-3 rounded-xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.MapPin, { className: "w-5 h-5 text-muted-foreground shrink-0" }), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1769
|
+
"input",
|
|
1770
|
+
{
|
|
1771
|
+
type: "text",
|
|
1772
|
+
className: "flex-1 bg-transparent text-sm text-foreground placeholder-muted-foreground focus:outline-none",
|
|
1773
|
+
placeholder: "Add location",
|
|
1774
|
+
value: formData.location || "",
|
|
1775
|
+
onChange: (e) => setFormData({ ...formData, location: e.target.value })
|
|
1776
|
+
}
|
|
1777
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "space-y-2" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.AlignLeft, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium text-foreground" }, "Notes")), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1778
|
+
"textarea",
|
|
1779
|
+
{
|
|
1780
|
+
className: "w-full bg-muted/20 border border-border/30 rounded-xl px-4 py-3 text-sm min-h-[100px] resize-none text-foreground placeholder-muted-foreground focus:border-primary focus:ring-2 focus:ring-primary/20 transition-all",
|
|
1781
|
+
placeholder: "Add description or notes...",
|
|
1782
|
+
value: formData.description || "",
|
|
1783
|
+
onChange: (e) => setFormData({ ...formData, description: e.target.value })
|
|
1784
|
+
}
|
|
1785
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-3 p-3 rounded-xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Repeat, { className: "w-5 h-5 text-muted-foreground shrink-0" }), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1786
|
+
"select",
|
|
1787
|
+
{
|
|
1788
|
+
className: "flex-1 bg-transparent text-sm text-foreground focus:outline-none cursor-pointer",
|
|
1789
|
+
value: formData.recurrence?.freq || "",
|
|
1790
|
+
onChange: (e) => {
|
|
1791
|
+
if (e.target.value === "") {
|
|
1792
|
+
setFormData({ ...formData, recurrence: void 0 });
|
|
1793
|
+
} else {
|
|
1794
|
+
setFormData({
|
|
1795
|
+
...formData,
|
|
1796
|
+
recurrence: {
|
|
1797
|
+
freq: e.target.value,
|
|
1798
|
+
interval: 1
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
},
|
|
1804
|
+
/* @__PURE__ */ React15__namespace.default.createElement("option", { value: "" }, "Does not repeat"),
|
|
1805
|
+
/* @__PURE__ */ React15__namespace.default.createElement("option", { value: "DAILY" }, "Daily"),
|
|
1806
|
+
/* @__PURE__ */ React15__namespace.default.createElement("option", { value: "WEEKLY" }, "Weekly"),
|
|
1807
|
+
/* @__PURE__ */ React15__namespace.default.createElement("option", { value: "MONTHLY" }, "Monthly"),
|
|
1808
|
+
/* @__PURE__ */ React15__namespace.default.createElement("option", { value: "YEARLY" }, "Yearly")
|
|
1809
|
+
)), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center justify-between p-3 rounded-xl bg-muted/20 border border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Tag, { className: "w-5 h-5 text-muted-foreground" }), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm text-foreground" }, "Calendar")), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "relative" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1810
|
+
"button",
|
|
1811
|
+
{
|
|
1812
|
+
type: "button",
|
|
1813
|
+
onClick: () => setIsCalendarDropdownOpen(!isCalendarDropdownOpen),
|
|
1814
|
+
className: "flex items-center gap-2 px-3 py-1.5 bg-background border border-border/50 rounded-lg text-sm hover:bg-accent/50 transition-all"
|
|
1815
|
+
},
|
|
1816
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
1817
|
+
"div",
|
|
1818
|
+
{
|
|
1819
|
+
className: "w-3 h-3 rounded-full",
|
|
1820
|
+
style: { backgroundColor: calendars?.find((c) => c.id === formData.calendarId)?.color || "#3b82f6" }
|
|
1821
|
+
}
|
|
1822
|
+
),
|
|
1823
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "font-medium" }, calendars?.find((c) => c.id === formData.calendarId)?.label || "Select"),
|
|
1824
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.ChevronDown, { className: "w-3.5 h-3.5 text-muted-foreground" })
|
|
1825
|
+
), isCalendarDropdownOpen && /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, null, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "fixed inset-0 z-40", onClick: () => setIsCalendarDropdownOpen(false) }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute right-0 bottom-full mb-2 w-48 bg-background border border-border rounded-xl shadow-xl z-50 py-1" }, calendars?.map((cal) => /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1826
|
+
"button",
|
|
1827
|
+
{
|
|
1828
|
+
key: cal.id,
|
|
1829
|
+
type: "button",
|
|
1830
|
+
className: "w-full flex items-center gap-3 px-3 py-2 hover:bg-accent transition-colors text-left",
|
|
1831
|
+
onClick: () => {
|
|
1832
|
+
setFormData({
|
|
1833
|
+
...formData,
|
|
1834
|
+
calendarId: cal.id,
|
|
1835
|
+
color: cal.color || formData.color
|
|
1836
|
+
});
|
|
1837
|
+
setIsCalendarDropdownOpen(false);
|
|
1838
|
+
}
|
|
1839
|
+
},
|
|
1840
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-3 h-3 rounded-full", style: { backgroundColor: cal.color } }),
|
|
1841
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", { className: "flex-1 text-sm" }, cal.label),
|
|
1842
|
+
formData.calendarId === cal.id && /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Check, { className: "w-4 h-4 text-primary" })
|
|
1843
|
+
)))))))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "px-5 py-4 border-t border-border/50 flex items-center justify-between bg-muted/10 shrink-0" }, mode === "edit" && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1844
|
+
"button",
|
|
1845
|
+
{
|
|
1846
|
+
type: "button",
|
|
1847
|
+
onClick: handleDelete,
|
|
1848
|
+
className: "flex items-center gap-2 px-4 py-2 text-destructive hover:bg-destructive/10 rounded-xl transition-all text-sm font-medium"
|
|
1849
|
+
},
|
|
1850
|
+
/* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Trash2, { className: "w-4 h-4" }),
|
|
1851
|
+
"Delete"
|
|
1852
|
+
), mode === "create" && /* @__PURE__ */ React15__namespace.default.createElement("div", null), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1853
|
+
"button",
|
|
1854
|
+
{
|
|
1855
|
+
type: "button",
|
|
1856
|
+
onClick: onClose,
|
|
1857
|
+
className: "px-4 py-2.5 text-muted-foreground hover:text-foreground font-medium text-sm rounded-xl transition-all hover:bg-accent"
|
|
1858
|
+
},
|
|
1859
|
+
"Cancel"
|
|
1860
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1861
|
+
"button",
|
|
1862
|
+
{
|
|
1863
|
+
type: "submit",
|
|
1864
|
+
className: "px-6 py-2.5 bg-primary hover:bg-primary/90 text-primary-foreground font-semibold text-sm rounded-xl shadow-md transition-all hover:shadow-lg hover:shadow-primary/20 active:scale-95"
|
|
1865
|
+
},
|
|
1866
|
+
mode === "edit" ? "Save Changes" : "Create Event"
|
|
1867
|
+
))));
|
|
1868
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(Modal, { isOpen, onClose, hideHeader: true, className: "p-0 overflow-hidden max-w-lg rounded-2xl shadow-2xl" }, /* @__PURE__ */ React15__namespace.default.createElement(framerMotion.AnimatePresence, { mode: "wait" }, mode === "view" && event ? /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1869
|
+
framerMotion.motion.div,
|
|
1870
|
+
{
|
|
1871
|
+
key: "view",
|
|
1872
|
+
initial: { opacity: 0 },
|
|
1873
|
+
animate: { opacity: 1 },
|
|
1874
|
+
exit: { opacity: 0 },
|
|
1875
|
+
transition: { duration: 0.15 }
|
|
1876
|
+
},
|
|
1877
|
+
renderViewMode()
|
|
1878
|
+
) : /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1879
|
+
framerMotion.motion.div,
|
|
1880
|
+
{
|
|
1881
|
+
key: "edit",
|
|
1882
|
+
initial: { opacity: 0 },
|
|
1883
|
+
animate: { opacity: 1 },
|
|
1884
|
+
exit: { opacity: 0 },
|
|
1885
|
+
transition: { duration: 0.15 }
|
|
1886
|
+
},
|
|
1887
|
+
renderEditMode()
|
|
1888
|
+
)));
|
|
1889
|
+
};
|
|
1890
|
+
var Skeleton = ({ className, style }) => /* @__PURE__ */ React15__namespace.default.createElement("div", { className: cn("animate-pulse bg-muted/40 rounded", className), style });
|
|
1891
|
+
var MonthViewSkeleton = () => /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "grid grid-cols-7 border-b border-border/50 bg-muted/10" }, Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: i, className: "py-3 px-2 text-center border-r border-border/30 last:border-r-0" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-4 w-8 mx-auto" })))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "grid grid-cols-7 flex-1" }, Array.from({ length: 35 }).map((_, i) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: i, className: "min-h-[120px] border-r border-b border-border/30 p-2" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-4 w-6 mb-2" }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "space-y-1" }, Math.random() > 0.5 && /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-5 w-full rounded-md" }), Math.random() > 0.7 && /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-5 w-3/4 rounded-md" }))))));
|
|
1892
|
+
var WeekViewSkeleton = () => /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex border-b border-border/50 bg-muted/10" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-16 p-3 border-r border-border/30" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-8 w-10 mx-auto" })), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 grid grid-cols-7" }, Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: i, className: "py-3 px-2 text-center border-r border-border/30 last:border-r-0" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-3 w-8 mx-auto mb-1" }), /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-8 w-8 mx-auto rounded-xl" }))))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-1", style: { height: "600px" } }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-16 border-r border-border/30" }, Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: i, className: "h-[60px] relative" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-4 w-10 absolute right-2 -translate-y-1/2" })))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 grid grid-cols-7 relative" }, Array.from({ length: 7 }).map((_, colIdx) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: colIdx, className: "relative border-r border-border/30 last:border-r-0" }, Array.from({ length: 10 }).map((_2, rowIdx) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: rowIdx, className: "h-[60px] border-b border-dashed border-border/20" })), Math.random() > 0.3 && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1893
|
+
Skeleton,
|
|
1894
|
+
{
|
|
1895
|
+
className: "absolute rounded-md",
|
|
1896
|
+
style: {
|
|
1897
|
+
top: `${Math.floor(Math.random() * 400)}px`,
|
|
1898
|
+
left: "4px",
|
|
1899
|
+
right: "4px",
|
|
1900
|
+
height: `${60 + Math.floor(Math.random() * 120)}px`
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
))))));
|
|
1904
|
+
var DayViewSkeleton = () => /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "px-6 py-4 border-b border-border/50 bg-muted/10 text-center" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-7 w-64 mx-auto" })), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-1", style: { height: "600px" } }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "w-20 border-r border-border/30 bg-muted/5" }, Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: i, className: "h-[80px] relative" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-4 w-12 absolute left-1/2 -translate-x-1/2 -translate-y-1/2" })))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 relative" }, Array.from({ length: 10 }).map((_, i) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: i, className: "h-[80px] border-b border-dashed border-border/20" })), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1905
|
+
Skeleton,
|
|
1906
|
+
{
|
|
1907
|
+
className: "absolute rounded-lg left-4 right-4",
|
|
1908
|
+
style: { top: "160px", height: "120px" }
|
|
1909
|
+
}
|
|
1910
|
+
), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1911
|
+
Skeleton,
|
|
1912
|
+
{
|
|
1913
|
+
className: "absolute rounded-lg left-4 right-4",
|
|
1914
|
+
style: { top: "400px", height: "80px" }
|
|
1915
|
+
}
|
|
1916
|
+
))));
|
|
1917
|
+
var AgendaViewSkeleton = () => /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden p-6" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "space-y-6" }, Array.from({ length: 4 }).map((_, dayIdx) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: dayIdx }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-5 w-40 mb-4" }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "space-y-3" }, Array.from({ length: 2 + Math.floor(Math.random() * 3) }).map((_2, eventIdx) => /* @__PURE__ */ React15__namespace.default.createElement("div", { key: eventIdx, className: "flex items-center gap-4 p-3 rounded-xl bg-muted/10" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-10 w-10 rounded-lg" }), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 space-y-2" }, /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-4 w-3/4" }), /* @__PURE__ */ React15__namespace.default.createElement(Skeleton, { className: "h-3 w-1/2" })))))))));
|
|
1918
|
+
var EventContextMenu = ({
|
|
1919
|
+
event,
|
|
1920
|
+
position,
|
|
1921
|
+
onClose,
|
|
1922
|
+
onEdit,
|
|
1923
|
+
onDelete,
|
|
1924
|
+
onDuplicate,
|
|
1925
|
+
customActions = [],
|
|
1926
|
+
translations
|
|
1927
|
+
}) => {
|
|
1928
|
+
const menuRef = React15.useRef(null);
|
|
1929
|
+
React15.useEffect(() => {
|
|
1930
|
+
const handleKeyDown = (e) => {
|
|
1931
|
+
if (e.key === "Escape") {
|
|
1932
|
+
onClose();
|
|
1933
|
+
}
|
|
1934
|
+
};
|
|
1935
|
+
if (position) {
|
|
1936
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1937
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1938
|
+
}
|
|
1939
|
+
}, [position, onClose]);
|
|
1940
|
+
React15.useEffect(() => {
|
|
1941
|
+
const handleClickOutside = (e) => {
|
|
1942
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) {
|
|
1943
|
+
onClose();
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
if (position) {
|
|
1947
|
+
setTimeout(() => {
|
|
1948
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
1949
|
+
}, 0);
|
|
1950
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
1951
|
+
}
|
|
1952
|
+
}, [position, onClose]);
|
|
1953
|
+
if (!event || !position) return null;
|
|
1954
|
+
const actions = [
|
|
1955
|
+
...onEdit ? [{
|
|
1956
|
+
id: "edit",
|
|
1957
|
+
label: translations?.edit || "Edit",
|
|
1958
|
+
icon: /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Edit3, { className: "w-4 h-4" }),
|
|
1959
|
+
onClick: () => {
|
|
1960
|
+
onEdit(event);
|
|
1961
|
+
onClose();
|
|
1962
|
+
}
|
|
1963
|
+
}] : [],
|
|
1964
|
+
...onDuplicate ? [{
|
|
1965
|
+
id: "duplicate",
|
|
1966
|
+
label: translations?.duplicate || "Duplicate",
|
|
1967
|
+
icon: /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Copy, { className: "w-4 h-4" }),
|
|
1968
|
+
onClick: () => {
|
|
1969
|
+
onDuplicate(event);
|
|
1970
|
+
onClose();
|
|
1971
|
+
}
|
|
1972
|
+
}] : [],
|
|
1973
|
+
...customActions,
|
|
1974
|
+
...onDelete ? [{
|
|
1975
|
+
id: "delete",
|
|
1976
|
+
label: translations?.delete || "Delete",
|
|
1977
|
+
icon: /* @__PURE__ */ React15__namespace.default.createElement(lucideReact.Trash2, { className: "w-4 h-4" }),
|
|
1978
|
+
onClick: () => {
|
|
1979
|
+
onDelete(event.id);
|
|
1980
|
+
onClose();
|
|
1981
|
+
},
|
|
1982
|
+
variant: "danger"
|
|
1983
|
+
}] : []
|
|
1984
|
+
];
|
|
1985
|
+
const adjustedPosition = {
|
|
1986
|
+
x: Math.min(position.x, window.innerWidth - 200),
|
|
1987
|
+
y: Math.min(position.y, window.innerHeight - (actions.length * 44 + 80))
|
|
1988
|
+
};
|
|
1989
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(framerMotion.AnimatePresence, null, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
1990
|
+
framerMotion.motion.div,
|
|
1991
|
+
{
|
|
1992
|
+
ref: menuRef,
|
|
1993
|
+
initial: { opacity: 0, scale: 0.95, y: -5 },
|
|
1994
|
+
animate: { opacity: 1, scale: 1, y: 0 },
|
|
1995
|
+
exit: { opacity: 0, scale: 0.95, y: -5 },
|
|
1996
|
+
transition: { duration: 0.15, ease: "easeOut" },
|
|
1997
|
+
className: "fixed z-[100] min-w-[180px] bg-background border-[0.5px] border-border rounded-xl shadow-xl overflow-hidden",
|
|
1998
|
+
style: {
|
|
1999
|
+
left: adjustedPosition.x,
|
|
2000
|
+
top: adjustedPosition.y
|
|
2001
|
+
}
|
|
2002
|
+
},
|
|
2003
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "px-3 py-2 border-b-[0.5px] border-border bg-muted/30" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2004
|
+
"div",
|
|
2005
|
+
{
|
|
2006
|
+
className: "w-2.5 h-2.5 rounded-full shrink-0",
|
|
2007
|
+
style: { backgroundColor: event.color || "var(--primary)" }
|
|
2008
|
+
}
|
|
2009
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("span", { className: "text-sm font-medium truncate text-foreground" }, event.title))),
|
|
2010
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "py-1" }, actions.map((action, index) => /* @__PURE__ */ React15__namespace.default.createElement(React15__namespace.default.Fragment, { key: action.id }, index > 0 && action.variant === "danger" && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-px bg-border my-1" }), /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2011
|
+
"button",
|
|
2012
|
+
{
|
|
2013
|
+
onClick: action.onClick,
|
|
2014
|
+
disabled: action.disabled,
|
|
2015
|
+
className: cn(
|
|
2016
|
+
"w-full flex items-center gap-3 px-3 py-2 text-sm transition-colors",
|
|
2017
|
+
action.disabled && "opacity-50 cursor-not-allowed",
|
|
2018
|
+
action.variant === "danger" ? "text-red-600 hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20" : "text-foreground hover:bg-accent"
|
|
2019
|
+
)
|
|
2020
|
+
},
|
|
2021
|
+
action.icon,
|
|
2022
|
+
/* @__PURE__ */ React15__namespace.default.createElement("span", null, action.label)
|
|
2023
|
+
))))
|
|
2024
|
+
));
|
|
2025
|
+
};
|
|
2026
|
+
var useEventContextMenu = () => {
|
|
2027
|
+
const [contextMenu, setContextMenu] = React15.useState({ event: null, position: null });
|
|
2028
|
+
const openContextMenu = React15.useCallback((event, e) => {
|
|
2029
|
+
e.preventDefault();
|
|
2030
|
+
e.stopPropagation();
|
|
2031
|
+
setContextMenu({
|
|
2032
|
+
event,
|
|
2033
|
+
position: { x: e.clientX, y: e.clientY }
|
|
2034
|
+
});
|
|
2035
|
+
}, []);
|
|
2036
|
+
const closeContextMenu = React15.useCallback(() => {
|
|
2037
|
+
setContextMenu({ event: null, position: null });
|
|
2038
|
+
}, []);
|
|
2039
|
+
return {
|
|
2040
|
+
contextMenuEvent: contextMenu.event,
|
|
2041
|
+
contextMenuPosition: contextMenu.position,
|
|
2042
|
+
openContextMenu,
|
|
2043
|
+
closeContextMenu
|
|
2044
|
+
};
|
|
2045
|
+
};
|
|
2046
|
+
|
|
2047
|
+
// src/lib/theme.ts
|
|
2048
|
+
function colorToHsl(color) {
|
|
2049
|
+
let r = 0, g = 0, b = 0;
|
|
2050
|
+
if (color.startsWith("#") || /^[0-9a-fA-F]{3,6}$/.test(color)) {
|
|
2051
|
+
let c = color.replace(/^#/, "");
|
|
2052
|
+
if (c.length === 3) {
|
|
2053
|
+
c = c.split("").map((char) => char + char).join("");
|
|
2054
|
+
}
|
|
2055
|
+
if (c.length !== 6) return null;
|
|
2056
|
+
r = parseInt(c.substring(0, 2), 16) / 255;
|
|
2057
|
+
g = parseInt(c.substring(2, 4), 16) / 255;
|
|
2058
|
+
b = parseInt(c.substring(4, 6), 16) / 255;
|
|
2059
|
+
} else if (color.startsWith("rgb")) {
|
|
2060
|
+
const match = color.match(/\d+/g);
|
|
2061
|
+
if (!match || match.length < 3) return null;
|
|
2062
|
+
r = parseInt(match[0]) / 255;
|
|
2063
|
+
g = parseInt(match[1]) / 255;
|
|
2064
|
+
b = parseInt(match[2]) / 255;
|
|
2065
|
+
} else {
|
|
2066
|
+
return null;
|
|
2067
|
+
}
|
|
2068
|
+
const max = Math.max(r, g, b);
|
|
2069
|
+
const min = Math.min(r, g, b);
|
|
2070
|
+
let h = 0;
|
|
2071
|
+
let s = 0;
|
|
2072
|
+
let l = (max + min) / 2;
|
|
2073
|
+
if (max !== min) {
|
|
2074
|
+
const d = max - min;
|
|
2075
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
2076
|
+
switch (max) {
|
|
2077
|
+
case r:
|
|
2078
|
+
h = (g - b) / d + (g < b ? 6 : 0);
|
|
2079
|
+
break;
|
|
2080
|
+
case g:
|
|
2081
|
+
h = (b - r) / d + 2;
|
|
2082
|
+
break;
|
|
2083
|
+
case b:
|
|
2084
|
+
h = (r - g) / d + 4;
|
|
2085
|
+
break;
|
|
2086
|
+
}
|
|
2087
|
+
h /= 6;
|
|
2088
|
+
}
|
|
2089
|
+
const hDeg = Math.round(h * 360);
|
|
2090
|
+
const sPct = Math.round(s * 100);
|
|
2091
|
+
const lPct = Math.round(l * 100);
|
|
2092
|
+
return `${hDeg} ${sPct}% ${lPct}%`;
|
|
2093
|
+
}
|
|
2094
|
+
function getThemeStyles(theme) {
|
|
2095
|
+
if (!theme) return {};
|
|
2096
|
+
const styles = {};
|
|
2097
|
+
if (theme.colors) {
|
|
2098
|
+
const mappings = {
|
|
2099
|
+
"--background": "background",
|
|
2100
|
+
"--foreground": "foreground",
|
|
2101
|
+
"--primary": "primary",
|
|
2102
|
+
"--secondary": "secondary",
|
|
2103
|
+
"--muted": "muted",
|
|
2104
|
+
"--accent": "accent",
|
|
2105
|
+
"--border": "border"
|
|
2106
|
+
};
|
|
2107
|
+
Object.entries(mappings).forEach(([cssVar, themeKey]) => {
|
|
2108
|
+
const colorValue = theme.colors?.[themeKey];
|
|
2109
|
+
if (colorValue) {
|
|
2110
|
+
const hsl = colorToHsl(colorValue);
|
|
2111
|
+
if (hsl) {
|
|
2112
|
+
styles[cssVar] = hsl;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
if (theme.borderRadius) {
|
|
2118
|
+
styles["--radius"] = theme.borderRadius;
|
|
2119
|
+
}
|
|
2120
|
+
if (theme.fontFamily) {
|
|
2121
|
+
styles["fontFamily"] = theme.fontFamily;
|
|
2122
|
+
}
|
|
2123
|
+
return styles;
|
|
2124
|
+
}
|
|
2125
|
+
var useCalendarLogic = ({
|
|
2126
|
+
events,
|
|
2127
|
+
view: controlledView,
|
|
2128
|
+
onViewChange: controlledOnViewChange,
|
|
2129
|
+
date: controlledDate,
|
|
2130
|
+
onDateChange: controlledOnDateChange,
|
|
2131
|
+
onEventClick,
|
|
2132
|
+
onEventUpdate,
|
|
2133
|
+
onEventCreate,
|
|
2134
|
+
onEventDelete,
|
|
2135
|
+
readOnly,
|
|
2136
|
+
timezone
|
|
2137
|
+
}) => {
|
|
2138
|
+
const [internalView, setInternalView] = React15.useState("week");
|
|
2139
|
+
const [internalDate, setInternalDate] = React15.useState(/* @__PURE__ */ new Date());
|
|
2140
|
+
const [isSidebarOpen, setIsSidebarOpen] = React15.useState(true);
|
|
2141
|
+
const [isModalOpen, setIsModalOpen] = React15.useState(false);
|
|
2142
|
+
const [selectedEvent, setSelectedEvent] = React15.useState(null);
|
|
2143
|
+
const [modalInitialDate, setModalInitialDate] = React15.useState(void 0);
|
|
2144
|
+
const view = controlledView ?? internalView;
|
|
2145
|
+
const currentDate = controlledDate ?? internalDate;
|
|
2146
|
+
const expandedEvents = React15.useMemo(() => {
|
|
2147
|
+
const allEvents = [];
|
|
2148
|
+
const rangeStart = dateFns.subDays(currentDate, 365);
|
|
2149
|
+
const rangeEnd = dateFns.addDays(currentDate, 365);
|
|
2150
|
+
events.forEach((event) => {
|
|
2151
|
+
if (event.recurrence) {
|
|
2152
|
+
try {
|
|
2153
|
+
const rule = new rrule.RRule({
|
|
2154
|
+
freq: rrule.RRule[event.recurrence.freq],
|
|
2155
|
+
interval: event.recurrence.interval || 1,
|
|
2156
|
+
dtstart: new Date(event.start),
|
|
2157
|
+
until: event.recurrence.until ? new Date(event.recurrence.until) : void 0,
|
|
2158
|
+
count: event.recurrence.count
|
|
2159
|
+
});
|
|
2160
|
+
const dates = rule.between(rangeStart, rangeEnd);
|
|
2161
|
+
dates.forEach((date) => {
|
|
2162
|
+
const duration = dateFns.differenceInMilliseconds(new Date(event.end), new Date(event.start));
|
|
2163
|
+
allEvents.push({
|
|
2164
|
+
...event,
|
|
2165
|
+
id: `${event.id}-${date.getTime()}`,
|
|
2166
|
+
// Unique ID for each instance
|
|
2167
|
+
originalEventId: event.id,
|
|
2168
|
+
// Reference to original
|
|
2169
|
+
start: date,
|
|
2170
|
+
end: new Date(date.getTime() + duration)
|
|
2171
|
+
});
|
|
2172
|
+
});
|
|
2173
|
+
} catch (e) {
|
|
2174
|
+
console.error("Error parsing recurrence rule", e);
|
|
2175
|
+
allEvents.push(event);
|
|
2176
|
+
}
|
|
2177
|
+
} else {
|
|
2178
|
+
allEvents.push(event);
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
return allEvents;
|
|
2182
|
+
}, [events, currentDate]);
|
|
2183
|
+
const handleViewChange = (newView) => {
|
|
2184
|
+
if (controlledOnViewChange) {
|
|
2185
|
+
controlledOnViewChange(newView);
|
|
2186
|
+
} else {
|
|
2187
|
+
setInternalView(newView);
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
const handleDateChange = (newDate) => {
|
|
2191
|
+
if (controlledOnDateChange) {
|
|
2192
|
+
controlledOnDateChange(newDate);
|
|
2193
|
+
} else {
|
|
2194
|
+
setInternalDate(newDate);
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
const handlePrev = () => {
|
|
2198
|
+
switch (view) {
|
|
2199
|
+
case "month":
|
|
2200
|
+
handleDateChange(dateFns.subMonths(currentDate, 1));
|
|
2201
|
+
break;
|
|
2202
|
+
case "week":
|
|
2203
|
+
handleDateChange(dateFns.subWeeks(currentDate, 1));
|
|
2204
|
+
break;
|
|
2205
|
+
case "day":
|
|
2206
|
+
case "resource":
|
|
2207
|
+
handleDateChange(dateFns.subDays(currentDate, 1));
|
|
2208
|
+
break;
|
|
2209
|
+
case "agenda":
|
|
2210
|
+
handleDateChange(dateFns.subDays(currentDate, 7));
|
|
2211
|
+
break;
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
const handleNext = () => {
|
|
2215
|
+
switch (view) {
|
|
2216
|
+
case "month":
|
|
2217
|
+
handleDateChange(dateFns.addMonths(currentDate, 1));
|
|
2218
|
+
break;
|
|
2219
|
+
case "week":
|
|
2220
|
+
handleDateChange(dateFns.addWeeks(currentDate, 1));
|
|
2221
|
+
break;
|
|
2222
|
+
case "day":
|
|
2223
|
+
case "resource":
|
|
2224
|
+
handleDateChange(dateFns.addDays(currentDate, 1));
|
|
2225
|
+
break;
|
|
2226
|
+
case "agenda":
|
|
2227
|
+
handleDateChange(dateFns.addDays(currentDate, 7));
|
|
2228
|
+
break;
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
const handleToday = () => {
|
|
2232
|
+
handleDateChange(/* @__PURE__ */ new Date());
|
|
2233
|
+
};
|
|
2234
|
+
const handleDateClick = (date) => {
|
|
2235
|
+
handleDateChange(date);
|
|
2236
|
+
handleViewChange("day");
|
|
2237
|
+
};
|
|
2238
|
+
const handleTimeSlotClick = (date) => {
|
|
2239
|
+
if (readOnly) return;
|
|
2240
|
+
setSelectedEvent(null);
|
|
2241
|
+
setModalInitialDate(date);
|
|
2242
|
+
setIsModalOpen(true);
|
|
2243
|
+
};
|
|
2244
|
+
const handleEventClickInternal = (event) => {
|
|
2245
|
+
if (onEventClick) {
|
|
2246
|
+
onEventClick(event);
|
|
2247
|
+
}
|
|
2248
|
+
if (!readOnly) {
|
|
2249
|
+
setSelectedEvent(event);
|
|
2250
|
+
setModalInitialDate(void 0);
|
|
2251
|
+
setIsModalOpen(true);
|
|
2252
|
+
}
|
|
2253
|
+
};
|
|
2254
|
+
const handleCreateEvent = () => {
|
|
2255
|
+
if (readOnly) return;
|
|
2256
|
+
setSelectedEvent(null);
|
|
2257
|
+
setModalInitialDate(/* @__PURE__ */ new Date());
|
|
2258
|
+
setIsModalOpen(true);
|
|
2259
|
+
};
|
|
2260
|
+
const handleModalSave = (eventData) => {
|
|
2261
|
+
let effectiveEventId = selectedEvent?.id;
|
|
2262
|
+
if (selectedEvent?.originalEventId) {
|
|
2263
|
+
effectiveEventId = selectedEvent.originalEventId;
|
|
2264
|
+
}
|
|
2265
|
+
if (effectiveEventId) {
|
|
2266
|
+
if (onEventUpdate) {
|
|
2267
|
+
onEventUpdate({
|
|
2268
|
+
...eventData,
|
|
2269
|
+
id: effectiveEventId
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
} else {
|
|
2273
|
+
if (onEventCreate) {
|
|
2274
|
+
onEventCreate(eventData);
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
};
|
|
2278
|
+
const handleModalDelete = (eventId) => {
|
|
2279
|
+
let effectiveEventId = eventId;
|
|
2280
|
+
if (selectedEvent?.originalEventId && selectedEvent.id === eventId) {
|
|
2281
|
+
effectiveEventId = selectedEvent.originalEventId;
|
|
2282
|
+
}
|
|
2283
|
+
if (onEventDelete) {
|
|
2284
|
+
onEventDelete(effectiveEventId);
|
|
2285
|
+
}
|
|
2286
|
+
};
|
|
2287
|
+
const handleDragEnd = (event) => {
|
|
2288
|
+
const { active, over } = event;
|
|
2289
|
+
if (!over) return;
|
|
2290
|
+
const activeEvent = active.data.current?.event;
|
|
2291
|
+
const overDate = over.data.current?.date;
|
|
2292
|
+
if (!activeEvent || !overDate) return;
|
|
2293
|
+
const originalStart = new Date(activeEvent.start);
|
|
2294
|
+
const originalEnd = new Date(activeEvent.end);
|
|
2295
|
+
const duration = dateFns.differenceInMilliseconds(originalEnd, originalStart);
|
|
2296
|
+
let newStart;
|
|
2297
|
+
if (view === "month") {
|
|
2298
|
+
if (timezone) {
|
|
2299
|
+
const zonedOriginal = dateFnsTz.toZonedTime(originalStart, timezone);
|
|
2300
|
+
const zonedNew = new Date(overDate);
|
|
2301
|
+
zonedNew.setHours(zonedOriginal.getHours());
|
|
2302
|
+
zonedNew.setMinutes(zonedOriginal.getMinutes());
|
|
2303
|
+
zonedNew.setSeconds(zonedOriginal.getSeconds());
|
|
2304
|
+
zonedNew.setMilliseconds(zonedOriginal.getMilliseconds());
|
|
2305
|
+
newStart = dateFnsTz.fromZonedTime(zonedNew, timezone);
|
|
2306
|
+
} else {
|
|
2307
|
+
newStart = new Date(overDate);
|
|
2308
|
+
newStart.setHours(originalStart.getHours());
|
|
2309
|
+
newStart.setMinutes(originalStart.getMinutes());
|
|
2310
|
+
newStart.setSeconds(originalStart.getSeconds());
|
|
2311
|
+
newStart.setMilliseconds(originalStart.getMilliseconds());
|
|
2312
|
+
}
|
|
2313
|
+
} else {
|
|
2314
|
+
if (timezone) {
|
|
2315
|
+
newStart = dateFnsTz.fromZonedTime(overDate, timezone);
|
|
2316
|
+
} else {
|
|
2317
|
+
newStart = new Date(overDate);
|
|
2318
|
+
}
|
|
2319
|
+
newStart.setSeconds(0);
|
|
2320
|
+
newStart.setMilliseconds(0);
|
|
2321
|
+
}
|
|
2322
|
+
const newEnd = new Date(newStart.getTime() + duration);
|
|
2323
|
+
let newResourceId = activeEvent.resourceId;
|
|
2324
|
+
const overResourceId = over.data.current?.resourceId;
|
|
2325
|
+
if (overResourceId) {
|
|
2326
|
+
newResourceId = overResourceId;
|
|
2327
|
+
}
|
|
2328
|
+
if (newStart.getTime() === originalStart.getTime() && newEnd.getTime() === originalEnd.getTime() && newResourceId === activeEvent.resourceId) {
|
|
2329
|
+
return;
|
|
2330
|
+
}
|
|
2331
|
+
if (onEventUpdate) {
|
|
2332
|
+
onEventUpdate({
|
|
2333
|
+
...activeEvent,
|
|
2334
|
+
start: newStart,
|
|
2335
|
+
end: newEnd,
|
|
2336
|
+
resourceId: newResourceId
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
};
|
|
2340
|
+
return {
|
|
2341
|
+
view,
|
|
2342
|
+
currentDate,
|
|
2343
|
+
isSidebarOpen,
|
|
2344
|
+
setIsSidebarOpen,
|
|
2345
|
+
isModalOpen,
|
|
2346
|
+
setIsModalOpen,
|
|
2347
|
+
selectedEvent,
|
|
2348
|
+
modalInitialDate,
|
|
2349
|
+
expandedEvents,
|
|
2350
|
+
handleViewChange,
|
|
2351
|
+
handleDateChange,
|
|
2352
|
+
handlePrev,
|
|
2353
|
+
handleNext,
|
|
2354
|
+
handleToday,
|
|
2355
|
+
handleDateClick,
|
|
2356
|
+
handleTimeSlotClick,
|
|
2357
|
+
handleEventClickInternal,
|
|
2358
|
+
handleCreateEvent,
|
|
2359
|
+
handleModalSave,
|
|
2360
|
+
handleModalDelete,
|
|
2361
|
+
handleDragEnd
|
|
2362
|
+
};
|
|
2363
|
+
};
|
|
2364
|
+
var useSwipeGesture = (options) => {
|
|
2365
|
+
const {
|
|
2366
|
+
onSwipeLeft,
|
|
2367
|
+
onSwipeRight,
|
|
2368
|
+
onSwipeUp,
|
|
2369
|
+
onSwipeDown,
|
|
2370
|
+
threshold = 100,
|
|
2371
|
+
restraint = 100,
|
|
2372
|
+
allowedTime = 500,
|
|
2373
|
+
enabled = true
|
|
2374
|
+
} = options;
|
|
2375
|
+
const touchInfoRef = React15.useRef(null);
|
|
2376
|
+
const elementRef = React15.useRef(null);
|
|
2377
|
+
const handleTouchStart = React15.useCallback((e) => {
|
|
2378
|
+
if (!enabled) return;
|
|
2379
|
+
const touch = e.touches[0];
|
|
2380
|
+
touchInfoRef.current = {
|
|
2381
|
+
startX: touch.clientX,
|
|
2382
|
+
startY: touch.clientY,
|
|
2383
|
+
startTime: Date.now()
|
|
2384
|
+
};
|
|
2385
|
+
}, [enabled]);
|
|
2386
|
+
const handleTouchEnd = React15.useCallback((e) => {
|
|
2387
|
+
if (!enabled || !touchInfoRef.current) return;
|
|
2388
|
+
const touch = e.changedTouches[0];
|
|
2389
|
+
const { startX, startY, startTime } = touchInfoRef.current;
|
|
2390
|
+
const distX = touch.clientX - startX;
|
|
2391
|
+
const distY = touch.clientY - startY;
|
|
2392
|
+
const elapsedTime = Date.now() - startTime;
|
|
2393
|
+
if (elapsedTime <= allowedTime) {
|
|
2394
|
+
if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint) {
|
|
2395
|
+
if (distX > 0) {
|
|
2396
|
+
onSwipeRight?.();
|
|
2397
|
+
} else {
|
|
2398
|
+
onSwipeLeft?.();
|
|
2399
|
+
}
|
|
2400
|
+
} else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint) {
|
|
2401
|
+
if (distY > 0) {
|
|
2402
|
+
onSwipeDown?.();
|
|
2403
|
+
} else {
|
|
2404
|
+
onSwipeUp?.();
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
touchInfoRef.current = null;
|
|
2409
|
+
}, [enabled, threshold, restraint, allowedTime, onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown]);
|
|
2410
|
+
React15.useEffect(() => {
|
|
2411
|
+
const element = elementRef.current;
|
|
2412
|
+
if (!element) return;
|
|
2413
|
+
element.addEventListener("touchstart", handleTouchStart, { passive: true });
|
|
2414
|
+
element.addEventListener("touchend", handleTouchEnd, { passive: true });
|
|
2415
|
+
return () => {
|
|
2416
|
+
element.removeEventListener("touchstart", handleTouchStart);
|
|
2417
|
+
element.removeEventListener("touchend", handleTouchEnd);
|
|
2418
|
+
};
|
|
2419
|
+
}, [handleTouchStart, handleTouchEnd]);
|
|
2420
|
+
return elementRef;
|
|
2421
|
+
};
|
|
2422
|
+
var useViewSwipe = (onPrev, onNext, enabled = true) => {
|
|
2423
|
+
return useSwipeGesture({
|
|
2424
|
+
onSwipeLeft: onNext,
|
|
2425
|
+
onSwipeRight: onPrev,
|
|
2426
|
+
threshold: 50,
|
|
2427
|
+
restraint: 100,
|
|
2428
|
+
allowedTime: 300,
|
|
2429
|
+
enabled
|
|
2430
|
+
});
|
|
2431
|
+
};
|
|
2432
|
+
|
|
2433
|
+
// src/index.tsx
|
|
2434
|
+
var Scheduler = ({
|
|
2435
|
+
events = [],
|
|
2436
|
+
view: controlledView,
|
|
2437
|
+
onViewChange: controlledOnViewChange,
|
|
2438
|
+
date: controlledDate,
|
|
2439
|
+
onDateChange: controlledOnDateChange,
|
|
2440
|
+
onEventClick,
|
|
2441
|
+
onEventCreate,
|
|
2442
|
+
onEventUpdate,
|
|
2443
|
+
onEventDelete,
|
|
2444
|
+
onEventResize,
|
|
2445
|
+
timezone,
|
|
2446
|
+
onTimezoneChange,
|
|
2447
|
+
className,
|
|
2448
|
+
theme,
|
|
2449
|
+
renderEventForm,
|
|
2450
|
+
readOnly,
|
|
2451
|
+
calendars,
|
|
2452
|
+
resources,
|
|
2453
|
+
eventTypes,
|
|
2454
|
+
onCalendarToggle,
|
|
2455
|
+
isLoading,
|
|
2456
|
+
isDarkMode,
|
|
2457
|
+
onThemeToggle,
|
|
2458
|
+
translations,
|
|
2459
|
+
// New Prop
|
|
2460
|
+
hideViewSwitcher,
|
|
2461
|
+
language,
|
|
2462
|
+
onLanguageChange,
|
|
2463
|
+
locale
|
|
2464
|
+
// Date-fns locale
|
|
2465
|
+
}) => {
|
|
2466
|
+
const [activeDragEvent, setActiveDragEvent] = React15.useState(null);
|
|
2467
|
+
const {
|
|
2468
|
+
contextMenuEvent,
|
|
2469
|
+
contextMenuPosition,
|
|
2470
|
+
closeContextMenu
|
|
2471
|
+
} = useEventContextMenu();
|
|
2472
|
+
const {
|
|
2473
|
+
view,
|
|
2474
|
+
currentDate,
|
|
2475
|
+
isSidebarOpen,
|
|
2476
|
+
setIsSidebarOpen,
|
|
2477
|
+
isModalOpen,
|
|
2478
|
+
setIsModalOpen,
|
|
2479
|
+
selectedEvent,
|
|
2480
|
+
modalInitialDate,
|
|
2481
|
+
handleViewChange,
|
|
2482
|
+
handleDateChange,
|
|
2483
|
+
handlePrev,
|
|
2484
|
+
handleNext,
|
|
2485
|
+
handleToday,
|
|
2486
|
+
handleDateClick,
|
|
2487
|
+
handleTimeSlotClick,
|
|
2488
|
+
handleEventClickInternal,
|
|
2489
|
+
handleCreateEvent,
|
|
2490
|
+
handleModalSave,
|
|
2491
|
+
handleModalDelete,
|
|
2492
|
+
handleDragEnd,
|
|
2493
|
+
expandedEvents
|
|
2494
|
+
// New export
|
|
2495
|
+
} = useCalendarLogic({
|
|
2496
|
+
events,
|
|
2497
|
+
view: controlledView,
|
|
2498
|
+
onViewChange: controlledOnViewChange,
|
|
2499
|
+
date: controlledDate,
|
|
2500
|
+
onDateChange: controlledOnDateChange,
|
|
2501
|
+
onEventClick,
|
|
2502
|
+
onEventUpdate,
|
|
2503
|
+
onEventCreate,
|
|
2504
|
+
onEventDelete,
|
|
2505
|
+
readOnly,
|
|
2506
|
+
timezone
|
|
2507
|
+
});
|
|
2508
|
+
const sensors = core.useSensors(
|
|
2509
|
+
core.useSensor(core.PointerSensor, {
|
|
2510
|
+
activationConstraint: {
|
|
2511
|
+
distance: 8
|
|
2512
|
+
}
|
|
2513
|
+
})
|
|
2514
|
+
);
|
|
2515
|
+
const gridSize = 15;
|
|
2516
|
+
const snapToGrid = modifiers.createSnapModifier(gridSize);
|
|
2517
|
+
const modifiers$1 = [snapToGrid, modifiers.restrictToWindowEdges];
|
|
2518
|
+
const dndSensors = readOnly ? [] : sensors;
|
|
2519
|
+
const handleEventResize = (event, newEnd) => {
|
|
2520
|
+
if (readOnly) return;
|
|
2521
|
+
if (onEventResize) {
|
|
2522
|
+
onEventResize(event, event.start, newEnd);
|
|
2523
|
+
}
|
|
2524
|
+
if (onEventUpdate) {
|
|
2525
|
+
onEventUpdate({
|
|
2526
|
+
...event,
|
|
2527
|
+
end: newEnd
|
|
2528
|
+
});
|
|
2529
|
+
}
|
|
2530
|
+
};
|
|
2531
|
+
const id = React15.useId();
|
|
2532
|
+
const swipeRef = useViewSwipe(handlePrev, handleNext, true);
|
|
2533
|
+
const t = {
|
|
2534
|
+
today: "Today",
|
|
2535
|
+
month: "Month",
|
|
2536
|
+
week: "Week",
|
|
2537
|
+
day: "Day",
|
|
2538
|
+
agenda: "Agenda",
|
|
2539
|
+
resource: "Resource",
|
|
2540
|
+
createEvent: "Create Event",
|
|
2541
|
+
editEvent: "Edit Event",
|
|
2542
|
+
delete: "Delete",
|
|
2543
|
+
save: "Save",
|
|
2544
|
+
cancel: "Cancel",
|
|
2545
|
+
title: "Title",
|
|
2546
|
+
start: "Start",
|
|
2547
|
+
end: "End",
|
|
2548
|
+
allDay: "All Day",
|
|
2549
|
+
description: "Description",
|
|
2550
|
+
repeat: "Repeat",
|
|
2551
|
+
noRepeat: "Does not repeat",
|
|
2552
|
+
selectCalendar: "Select Calendar",
|
|
2553
|
+
selectType: "Select Type",
|
|
2554
|
+
daily: "Daily",
|
|
2555
|
+
weekly: "Weekly",
|
|
2556
|
+
monthly: "Monthly",
|
|
2557
|
+
yearly: "Yearly",
|
|
2558
|
+
...translations
|
|
2559
|
+
};
|
|
2560
|
+
const handleDragStart = (event) => {
|
|
2561
|
+
const { active } = event;
|
|
2562
|
+
const draggedEvent = expandedEvents.find((e) => e.id === active.id);
|
|
2563
|
+
if (draggedEvent) {
|
|
2564
|
+
setActiveDragEvent(draggedEvent);
|
|
2565
|
+
}
|
|
2566
|
+
};
|
|
2567
|
+
const onDragEndWrapper = (event) => {
|
|
2568
|
+
setActiveDragEvent(null);
|
|
2569
|
+
handleDragEnd(event);
|
|
2570
|
+
};
|
|
2571
|
+
const getDragHeight = () => {
|
|
2572
|
+
if (!activeDragEvent) return void 0;
|
|
2573
|
+
if (view === "resource") {
|
|
2574
|
+
return 80;
|
|
2575
|
+
}
|
|
2576
|
+
if (view !== "week" && view !== "day") return void 0;
|
|
2577
|
+
const duration = dateFns.differenceInMinutes(activeDragEvent.end, activeDragEvent.start);
|
|
2578
|
+
const hourHeight = view === "day" ? 80 : 60;
|
|
2579
|
+
return duration / 60 * hourHeight;
|
|
2580
|
+
};
|
|
2581
|
+
const getDragWidth = () => {
|
|
2582
|
+
if (view === "month") return "100%";
|
|
2583
|
+
if (view === "resource" && activeDragEvent) {
|
|
2584
|
+
const duration = dateFns.differenceInMinutes(activeDragEvent.end, activeDragEvent.start);
|
|
2585
|
+
const width = duration / 60 * 100;
|
|
2586
|
+
return `${Math.max(width, 4)}px`;
|
|
2587
|
+
}
|
|
2588
|
+
return "150px";
|
|
2589
|
+
};
|
|
2590
|
+
const filteredEvents = React15.useMemo(() => {
|
|
2591
|
+
if (!calendars) return expandedEvents;
|
|
2592
|
+
const activeCalendarIds = calendars.filter((c) => c.active !== false).map((c) => c.id);
|
|
2593
|
+
return expandedEvents.filter((e) => {
|
|
2594
|
+
if (!e.calendarId) return true;
|
|
2595
|
+
return activeCalendarIds.includes(e.calendarId);
|
|
2596
|
+
});
|
|
2597
|
+
}, [expandedEvents, calendars]);
|
|
2598
|
+
return /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2599
|
+
core.DndContext,
|
|
2600
|
+
{
|
|
2601
|
+
id,
|
|
2602
|
+
sensors: dndSensors,
|
|
2603
|
+
onDragStart: handleDragStart,
|
|
2604
|
+
onDragEnd: onDragEndWrapper,
|
|
2605
|
+
modifiers: modifiers$1
|
|
2606
|
+
},
|
|
2607
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
2608
|
+
"div",
|
|
2609
|
+
{
|
|
2610
|
+
className: cn("flex flex-col h-full bg-background text-foreground relative", className),
|
|
2611
|
+
style: getThemeStyles(theme)
|
|
2612
|
+
},
|
|
2613
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
2614
|
+
CalendarHeader,
|
|
2615
|
+
{
|
|
2616
|
+
currentDate,
|
|
2617
|
+
onPrev: handlePrev,
|
|
2618
|
+
onNext: handleNext,
|
|
2619
|
+
onToday: handleToday,
|
|
2620
|
+
view,
|
|
2621
|
+
onViewChange: handleViewChange,
|
|
2622
|
+
onMenuClick: () => setIsSidebarOpen(!isSidebarOpen),
|
|
2623
|
+
isDarkMode,
|
|
2624
|
+
onThemeToggle,
|
|
2625
|
+
translations: t,
|
|
2626
|
+
hideViewSwitcher,
|
|
2627
|
+
language,
|
|
2628
|
+
onLanguageChange,
|
|
2629
|
+
locale
|
|
2630
|
+
}
|
|
2631
|
+
),
|
|
2632
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex flex-1 overflow-hidden" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2633
|
+
framerMotion.motion.div,
|
|
2634
|
+
{
|
|
2635
|
+
className: cn(
|
|
2636
|
+
"hidden md:block overflow-hidden"
|
|
2637
|
+
),
|
|
2638
|
+
initial: false,
|
|
2639
|
+
animate: {
|
|
2640
|
+
width: isSidebarOpen ? 256 : 0,
|
|
2641
|
+
opacity: isSidebarOpen ? 1 : 0
|
|
2642
|
+
},
|
|
2643
|
+
transition: {
|
|
2644
|
+
duration: 0.3,
|
|
2645
|
+
ease: [0.4, 0, 0.2, 1]
|
|
2646
|
+
}
|
|
2647
|
+
},
|
|
2648
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
2649
|
+
Sidebar,
|
|
2650
|
+
{
|
|
2651
|
+
currentDate,
|
|
2652
|
+
onDateChange: handleDateChange,
|
|
2653
|
+
onViewChange: handleViewChange,
|
|
2654
|
+
onEventCreate: handleCreateEvent,
|
|
2655
|
+
timezone,
|
|
2656
|
+
onTimezoneChange,
|
|
2657
|
+
className: "w-full h-full",
|
|
2658
|
+
readOnly,
|
|
2659
|
+
calendars,
|
|
2660
|
+
onCalendarToggle,
|
|
2661
|
+
translations: t
|
|
2662
|
+
}
|
|
2663
|
+
)
|
|
2664
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 flex flex-col overflow-hidden relative" }, isLoading ? /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "flex-1 overflow-auto p-0 md:p-4" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-full min-w-full" }, view === "month" && /* @__PURE__ */ React15__namespace.default.createElement(MonthViewSkeleton, null), view === "week" && /* @__PURE__ */ React15__namespace.default.createElement(WeekViewSkeleton, null), view === "day" && /* @__PURE__ */ React15__namespace.default.createElement(DayViewSkeleton, null), view === "agenda" && /* @__PURE__ */ React15__namespace.default.createElement(AgendaViewSkeleton, null), view === "resource" && /* @__PURE__ */ React15__namespace.default.createElement(WeekViewSkeleton, null))) : /* @__PURE__ */ React15__namespace.default.createElement("div", { ref: swipeRef, className: "flex-1 overflow-auto p-0 md:p-4 touch-pan-y" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "h-full min-w-full" }, /* @__PURE__ */ React15__namespace.default.createElement(framerMotion.AnimatePresence, { mode: "wait", initial: false }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2665
|
+
framerMotion.motion.div,
|
|
2666
|
+
{
|
|
2667
|
+
key: `${view}-${currentDate.toISOString()}-${timezone || "local"}`,
|
|
2668
|
+
initial: { opacity: 0, scale: 0.98, y: 15 },
|
|
2669
|
+
animate: {
|
|
2670
|
+
opacity: 1,
|
|
2671
|
+
scale: 1,
|
|
2672
|
+
y: 0,
|
|
2673
|
+
transition: {
|
|
2674
|
+
duration: 0.25,
|
|
2675
|
+
ease: [0.25, 0.1, 0.25, 1]
|
|
2676
|
+
}
|
|
2677
|
+
},
|
|
2678
|
+
exit: {
|
|
2679
|
+
opacity: 0,
|
|
2680
|
+
scale: 0.98,
|
|
2681
|
+
y: -10,
|
|
2682
|
+
transition: {
|
|
2683
|
+
duration: 0.15,
|
|
2684
|
+
ease: [0.25, 0.1, 0.25, 1]
|
|
2685
|
+
}
|
|
2686
|
+
},
|
|
2687
|
+
className: "h-full"
|
|
2688
|
+
},
|
|
2689
|
+
view === "month" && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2690
|
+
MonthView,
|
|
2691
|
+
{
|
|
2692
|
+
currentDate,
|
|
2693
|
+
events: filteredEvents,
|
|
2694
|
+
onEventClick: handleEventClickInternal,
|
|
2695
|
+
onDateClick: handleDateClick,
|
|
2696
|
+
timezone,
|
|
2697
|
+
locale
|
|
2698
|
+
}
|
|
2699
|
+
),
|
|
2700
|
+
view === "week" && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2701
|
+
WeekView,
|
|
2702
|
+
{
|
|
2703
|
+
currentDate,
|
|
2704
|
+
events: filteredEvents,
|
|
2705
|
+
onEventClick: handleEventClickInternal,
|
|
2706
|
+
onTimeSlotClick: handleTimeSlotClick,
|
|
2707
|
+
onEventResize: handleEventResize,
|
|
2708
|
+
timezone,
|
|
2709
|
+
locale,
|
|
2710
|
+
readonly: readOnly
|
|
2711
|
+
}
|
|
2712
|
+
),
|
|
2713
|
+
view === "day" && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2714
|
+
DayView,
|
|
2715
|
+
{
|
|
2716
|
+
currentDate,
|
|
2717
|
+
events: filteredEvents,
|
|
2718
|
+
onEventClick: handleEventClickInternal,
|
|
2719
|
+
onTimeSlotClick: handleTimeSlotClick,
|
|
2720
|
+
onEventResize: handleEventResize,
|
|
2721
|
+
timezone,
|
|
2722
|
+
locale,
|
|
2723
|
+
readonly: readOnly
|
|
2724
|
+
}
|
|
2725
|
+
),
|
|
2726
|
+
view === "agenda" && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2727
|
+
AgendaView,
|
|
2728
|
+
{
|
|
2729
|
+
currentDate,
|
|
2730
|
+
events: filteredEvents,
|
|
2731
|
+
onEventClick: handleEventClickInternal,
|
|
2732
|
+
onCreateEvent: handleCreateEvent
|
|
2733
|
+
}
|
|
2734
|
+
),
|
|
2735
|
+
view === "resource" && resources && /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2736
|
+
ResourceView,
|
|
2737
|
+
{
|
|
2738
|
+
currentDate,
|
|
2739
|
+
events: filteredEvents,
|
|
2740
|
+
resources,
|
|
2741
|
+
onEventClick: handleEventClickInternal,
|
|
2742
|
+
onTimeSlotClick: (date, resourceId) => {
|
|
2743
|
+
if (readOnly) return;
|
|
2744
|
+
handleTimeSlotClick(date);
|
|
2745
|
+
},
|
|
2746
|
+
locale
|
|
2747
|
+
}
|
|
2748
|
+
)
|
|
2749
|
+
)))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "md:hidden absolute bottom-6 right-6 z-50" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2750
|
+
"button",
|
|
2751
|
+
{
|
|
2752
|
+
onClick: handleCreateEvent,
|
|
2753
|
+
className: "w-14 h-14 bg-blue-600 rounded-full shadow-lg flex items-center justify-center text-white active:scale-90 transition-transform"
|
|
2754
|
+
},
|
|
2755
|
+
/* @__PURE__ */ React15__namespace.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React15__namespace.default.createElement("path", { d: "M5 12h14" }), /* @__PURE__ */ React15__namespace.default.createElement("path", { d: "M12 5v14" }))
|
|
2756
|
+
)))),
|
|
2757
|
+
renderEventForm ? renderEventForm({
|
|
2758
|
+
isOpen: isModalOpen,
|
|
2759
|
+
onClose: () => setIsModalOpen(false),
|
|
2760
|
+
event: selectedEvent,
|
|
2761
|
+
initialDate: modalInitialDate,
|
|
2762
|
+
onSave: handleModalSave,
|
|
2763
|
+
onDelete: handleModalDelete
|
|
2764
|
+
}) : /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2765
|
+
EventModal,
|
|
2766
|
+
{
|
|
2767
|
+
isOpen: isModalOpen,
|
|
2768
|
+
onClose: () => setIsModalOpen(false),
|
|
2769
|
+
event: selectedEvent,
|
|
2770
|
+
initialDate: modalInitialDate,
|
|
2771
|
+
onSave: handleModalSave,
|
|
2772
|
+
onDelete: handleModalDelete,
|
|
2773
|
+
calendars,
|
|
2774
|
+
eventTypes,
|
|
2775
|
+
translations: t
|
|
2776
|
+
}
|
|
2777
|
+
),
|
|
2778
|
+
/* @__PURE__ */ React15__namespace.default.createElement(
|
|
2779
|
+
EventContextMenu,
|
|
2780
|
+
{
|
|
2781
|
+
event: contextMenuEvent,
|
|
2782
|
+
position: contextMenuPosition,
|
|
2783
|
+
onClose: closeContextMenu,
|
|
2784
|
+
onEdit: (event) => {
|
|
2785
|
+
handleEventClickInternal(event);
|
|
2786
|
+
closeContextMenu();
|
|
2787
|
+
},
|
|
2788
|
+
onDelete: (eventId) => {
|
|
2789
|
+
onEventDelete?.(eventId);
|
|
2790
|
+
closeContextMenu();
|
|
2791
|
+
},
|
|
2792
|
+
onDuplicate: (event) => {
|
|
2793
|
+
const duplicatedEvent = {
|
|
2794
|
+
...event,
|
|
2795
|
+
id: `${event.id}-copy-${Date.now()}`,
|
|
2796
|
+
title: `${event.title} (Copy)`,
|
|
2797
|
+
start: new Date(event.start.getTime() + 24 * 60 * 60 * 1e3),
|
|
2798
|
+
// +1 day
|
|
2799
|
+
end: new Date(event.end.getTime() + 24 * 60 * 60 * 1e3)
|
|
2800
|
+
};
|
|
2801
|
+
onEventCreate?.(duplicatedEvent);
|
|
2802
|
+
closeContextMenu();
|
|
2803
|
+
},
|
|
2804
|
+
translations: {
|
|
2805
|
+
edit: t.editEvent || "Edit",
|
|
2806
|
+
delete: t.delete || "Delete",
|
|
2807
|
+
duplicate: "Duplicate"
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
),
|
|
2811
|
+
/* @__PURE__ */ React15__namespace.default.createElement(core.DragOverlay, { dropAnimation: null }, activeDragEvent ? /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2812
|
+
"div",
|
|
2813
|
+
{
|
|
2814
|
+
className: cn(
|
|
2815
|
+
"rounded-lg shadow-2xl border-2 overflow-hidden cursor-grabbing transition-transform",
|
|
2816
|
+
"backdrop-blur-sm",
|
|
2817
|
+
!activeDragEvent.color && "bg-primary/90 border-primary/60 text-primary-foreground"
|
|
2818
|
+
),
|
|
2819
|
+
style: {
|
|
2820
|
+
backgroundColor: activeDragEvent.color ? `${activeDragEvent.color}e0` : void 0,
|
|
2821
|
+
borderColor: activeDragEvent.color ? `${activeDragEvent.color}80` : void 0,
|
|
2822
|
+
color: activeDragEvent.color ? "#fff" : void 0,
|
|
2823
|
+
width: getDragWidth(),
|
|
2824
|
+
height: getDragHeight() ? `${getDragHeight()}px` : void 0,
|
|
2825
|
+
boxShadow: `0 20px 40px -15px ${activeDragEvent.color || "var(--primary)"}40, 0 10px 20px -10px rgba(0,0,0,0.2)`,
|
|
2826
|
+
transform: "rotate(-2deg) scale(1.02)"
|
|
2827
|
+
}
|
|
2828
|
+
},
|
|
2829
|
+
/* @__PURE__ */ React15__namespace.default.createElement("div", { className: "p-2.5 h-full flex flex-col" }, /* @__PURE__ */ React15__namespace.default.createElement(
|
|
2830
|
+
"div",
|
|
2831
|
+
{
|
|
2832
|
+
className: "absolute left-0 top-0 bottom-0 w-1 rounded-l-lg",
|
|
2833
|
+
style: { backgroundColor: activeDragEvent.color || "var(--primary)" }
|
|
2834
|
+
}
|
|
2835
|
+
), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "pl-2" }, /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "font-semibold truncate text-sm" }, activeDragEvent.title), (view === "week" || view === "day") && getDragHeight() && getDragHeight() > 40 && /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "text-xs opacity-80 mt-0.5 flex items-center gap-1" }, /* @__PURE__ */ React15__namespace.default.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React15__namespace.default.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ React15__namespace.default.createElement("path", { d: "M12 6v6l4 2" })), dateFns.format(activeDragEvent.start, "h:mm a"), " - ", dateFns.format(activeDragEvent.end, "h:mm a"))), /* @__PURE__ */ React15__namespace.default.createElement("div", { className: "absolute bottom-1.5 right-1.5 opacity-60" }, /* @__PURE__ */ React15__namespace.default.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React15__namespace.default.createElement("circle", { cx: "9", cy: "5", r: "1.5" }), /* @__PURE__ */ React15__namespace.default.createElement("circle", { cx: "15", cy: "5", r: "1.5" }), /* @__PURE__ */ React15__namespace.default.createElement("circle", { cx: "9", cy: "12", r: "1.5" }), /* @__PURE__ */ React15__namespace.default.createElement("circle", { cx: "15", cy: "12", r: "1.5" }), /* @__PURE__ */ React15__namespace.default.createElement("circle", { cx: "9", cy: "19", r: "1.5" }), /* @__PURE__ */ React15__namespace.default.createElement("circle", { cx: "15", cy: "19", r: "1.5" }))))
|
|
2836
|
+
) : null)
|
|
2837
|
+
)
|
|
2838
|
+
);
|
|
2839
|
+
};
|
|
2840
|
+
|
|
2841
|
+
exports.Scheduler = Scheduler;
|
|
2842
|
+
exports.cn = cn;
|
|
2843
|
+
//# sourceMappingURL=index.js.map
|
|
2844
|
+
//# sourceMappingURL=index.js.map
|