@hectorbliss/denik-calendar 0.0.2 → 0.0.4
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/README.md +137 -47
- package/dist/index.cjs +306 -57
- package/dist/index.d.cts +146 -2
- package/dist/index.d.ts +146 -2
- package/dist/index.js +306 -59
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, RefObject } from 'react';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Resource definition for day/resource view mode
|
|
6
|
+
* Use this to represent courts, rooms, employees, etc.
|
|
7
|
+
*/
|
|
8
|
+
interface Resource {
|
|
9
|
+
/** Unique identifier for the resource */
|
|
10
|
+
id: string;
|
|
11
|
+
/** Display name */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Optional icon/avatar */
|
|
14
|
+
icon?: ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Props passed to custom column header renderer
|
|
18
|
+
* Use this to build custom headers for resources (courts, rooms, employees, etc.)
|
|
19
|
+
*/
|
|
20
|
+
interface ColumnHeaderProps {
|
|
21
|
+
/** The date for this column */
|
|
22
|
+
date: Date;
|
|
23
|
+
/** Column index */
|
|
24
|
+
index: number;
|
|
25
|
+
/** Whether this column represents today */
|
|
26
|
+
isToday: boolean;
|
|
27
|
+
/** The configured locale */
|
|
28
|
+
locale: string;
|
|
29
|
+
/** Resource data (only in resource mode) */
|
|
30
|
+
resource?: Resource;
|
|
31
|
+
}
|
|
4
32
|
/**
|
|
5
33
|
* Generic calendar event - decoupled from any ORM
|
|
6
34
|
*/
|
|
@@ -13,6 +41,8 @@ interface CalendarEvent {
|
|
|
13
41
|
service?: {
|
|
14
42
|
name: string;
|
|
15
43
|
} | null;
|
|
44
|
+
/** Resource ID for day/resource view (court, room, etc.) */
|
|
45
|
+
resourceId?: string;
|
|
16
46
|
}
|
|
17
47
|
/**
|
|
18
48
|
* Calendar configuration options
|
|
@@ -30,6 +60,23 @@ interface CalendarConfig {
|
|
|
30
60
|
edit?: ReactNode;
|
|
31
61
|
close?: ReactNode;
|
|
32
62
|
};
|
|
63
|
+
/**
|
|
64
|
+
* Custom renderer for column headers.
|
|
65
|
+
* Use this to display resources (courts, rooms, employees) instead of weekdays.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* // Padel courts
|
|
69
|
+
* renderColumnHeader: ({ index }) => <span>Court {index + 1}</span>
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* // With custom styling
|
|
73
|
+
* renderColumnHeader: ({ date, isToday }) => (
|
|
74
|
+
* <div className={isToday ? "font-bold" : ""}>
|
|
75
|
+
* {date.toLocaleDateString("en", { weekday: "short" })}
|
|
76
|
+
* </div>
|
|
77
|
+
* )
|
|
78
|
+
*/
|
|
79
|
+
renderColumnHeader?: (props: ColumnHeaderProps) => ReactNode;
|
|
33
80
|
}
|
|
34
81
|
/**
|
|
35
82
|
* Calendar component props
|
|
@@ -39,6 +86,18 @@ interface CalendarProps {
|
|
|
39
86
|
date?: Date;
|
|
40
87
|
/** Array of events to display */
|
|
41
88
|
events?: CalendarEvent[];
|
|
89
|
+
/**
|
|
90
|
+
* Resources for day/resource view mode.
|
|
91
|
+
* When provided, columns represent resources instead of weekdays.
|
|
92
|
+
* Events are filtered by date and grouped by resourceId.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* resources={[
|
|
96
|
+
* { id: "court-1", name: "Cancha 1", icon: <PadelIcon /> },
|
|
97
|
+
* { id: "court-2", name: "Cancha 2", icon: <PadelIcon /> },
|
|
98
|
+
* ]}
|
|
99
|
+
*/
|
|
100
|
+
resources?: Resource[];
|
|
42
101
|
/** Callback when an event is clicked */
|
|
43
102
|
onEventClick?: (event: CalendarEvent) => void;
|
|
44
103
|
/** Callback when an event is moved via drag & drop */
|
|
@@ -53,7 +112,92 @@ interface CalendarProps {
|
|
|
53
112
|
config?: CalendarConfig;
|
|
54
113
|
}
|
|
55
114
|
|
|
56
|
-
declare function Calendar({ date, events, onEventClick, onNewEvent, onEventMove, onAddBlock, onRemoveBlock, config, }: CalendarProps): react_jsx_runtime.JSX.Element;
|
|
115
|
+
declare function Calendar({ date, events, resources, onEventClick, onNewEvent, onEventMove, onAddBlock, onRemoveBlock, config, }: CalendarProps): react_jsx_runtime.JSX.Element;
|
|
116
|
+
|
|
117
|
+
type CalendarView = "week" | "day";
|
|
118
|
+
interface UseCalendarControlsOptions {
|
|
119
|
+
/** Initial date (default: today) */
|
|
120
|
+
initialDate?: Date;
|
|
121
|
+
/** Initial view mode (default: "week") */
|
|
122
|
+
initialView?: CalendarView;
|
|
123
|
+
/** Locale for date formatting (default: "es-MX") */
|
|
124
|
+
locale?: string;
|
|
125
|
+
}
|
|
126
|
+
interface CalendarControls$1 {
|
|
127
|
+
/** Current date */
|
|
128
|
+
date: Date;
|
|
129
|
+
/** Current view mode */
|
|
130
|
+
view: CalendarView;
|
|
131
|
+
/** Week days array (Mon-Sun) */
|
|
132
|
+
week: Date[];
|
|
133
|
+
/** Formatted label for current date/week */
|
|
134
|
+
label: string;
|
|
135
|
+
/** Navigate to today */
|
|
136
|
+
goToToday: () => void;
|
|
137
|
+
/** Navigate to previous period (week or day) */
|
|
138
|
+
goToPrev: () => void;
|
|
139
|
+
/** Navigate to next period (week or day) */
|
|
140
|
+
goToNext: () => void;
|
|
141
|
+
/** Toggle between week and day view */
|
|
142
|
+
toggleView: () => void;
|
|
143
|
+
/** Set specific date */
|
|
144
|
+
setDate: (date: Date) => void;
|
|
145
|
+
/** Set specific view */
|
|
146
|
+
setView: (view: CalendarView) => void;
|
|
147
|
+
/** Check if current date is today */
|
|
148
|
+
isToday: boolean;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Hook for calendar navigation controls
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* const controls = useCalendarControls();
|
|
155
|
+
*
|
|
156
|
+
* <button onClick={controls.goToToday}>HOY</button>
|
|
157
|
+
* <button onClick={controls.goToPrev}>←</button>
|
|
158
|
+
* <button onClick={controls.goToNext}>→</button>
|
|
159
|
+
* <span>{controls.label}</span>
|
|
160
|
+
*
|
|
161
|
+
* <Calendar
|
|
162
|
+
* date={controls.date}
|
|
163
|
+
* resources={controls.view === "day" ? courts : undefined}
|
|
164
|
+
* />
|
|
165
|
+
*/
|
|
166
|
+
declare function useCalendarControls(options?: UseCalendarControlsOptions): CalendarControls$1;
|
|
167
|
+
|
|
168
|
+
interface CalendarControlsProps {
|
|
169
|
+
/** Controls from useCalendarControls hook */
|
|
170
|
+
controls: CalendarControls$1;
|
|
171
|
+
/** Custom "Today" button label */
|
|
172
|
+
todayLabel?: string;
|
|
173
|
+
/** Custom "Week" label */
|
|
174
|
+
weekLabel?: string;
|
|
175
|
+
/** Custom "Day" label */
|
|
176
|
+
dayLabel?: string;
|
|
177
|
+
/** Show view toggle (default: true) */
|
|
178
|
+
showViewToggle?: boolean;
|
|
179
|
+
/** Custom prev icon */
|
|
180
|
+
prevIcon?: ReactNode;
|
|
181
|
+
/** Custom next icon */
|
|
182
|
+
nextIcon?: ReactNode;
|
|
183
|
+
/** Additional action buttons (export, add, etc.) */
|
|
184
|
+
actions?: ReactNode;
|
|
185
|
+
/** Custom class name */
|
|
186
|
+
className?: string;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Pre-built calendar controls component
|
|
190
|
+
*
|
|
191
|
+
* @example
|
|
192
|
+
* const controls = useCalendarControls();
|
|
193
|
+
*
|
|
194
|
+
* <CalendarControls
|
|
195
|
+
* controls={controls}
|
|
196
|
+
* actions={<button>Add Event</button>}
|
|
197
|
+
* />
|
|
198
|
+
* <Calendar date={controls.date} />
|
|
199
|
+
*/
|
|
200
|
+
declare function CalendarControls({ controls, todayLabel, weekLabel, dayLabel, showViewToggle, prevIcon, nextIcon, actions, className, }: CalendarControlsProps): react_jsx_runtime.JSX.Element;
|
|
57
201
|
|
|
58
202
|
/**
|
|
59
203
|
* Hook for managing calendar events - overlap detection, filtering, and availability
|
|
@@ -128,4 +272,4 @@ declare function useClickOutside<T extends HTMLElement>({ isActive, onOutsideCli
|
|
|
128
272
|
*/
|
|
129
273
|
declare function formatDate(date: Date, locale?: string): string;
|
|
130
274
|
|
|
131
|
-
export { Calendar, type CalendarConfig, type CalendarEvent, type CalendarProps, Calendar as SimpleBigWeekView, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarEvents, useClickOutside, useEventOverlap };
|
|
275
|
+
export { Calendar, type CalendarConfig, CalendarControls, type CalendarControlsProps, type CalendarControls$1 as CalendarControlsState, type CalendarEvent, type CalendarProps, type CalendarView, type ColumnHeaderProps, type Resource, Calendar as SimpleBigWeekView, type UseCalendarControlsOptions, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarControls, useCalendarEvents, useClickOutside, useEventOverlap };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useRef, useEffect, useState } from 'react';
|
|
1
|
+
import { useCallback, useRef, useEffect, useState, useMemo } from 'react';
|
|
2
2
|
import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter, DragOverlay, useDroppable, useDraggable } from '@dnd-kit/core';
|
|
3
3
|
import { CSS } from '@dnd-kit/utilities';
|
|
4
4
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
@@ -215,21 +215,40 @@ var cn = (...classes) => classes.filter(Boolean).join(" ");
|
|
|
215
215
|
var DefaultTrashIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" }) });
|
|
216
216
|
var DefaultEditIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z" }) });
|
|
217
217
|
var DefaultCloseIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" }) });
|
|
218
|
-
var DayHeader = ({
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
218
|
+
var DayHeader = ({
|
|
219
|
+
date,
|
|
220
|
+
locale,
|
|
221
|
+
index,
|
|
222
|
+
resource,
|
|
223
|
+
renderColumnHeader
|
|
224
|
+
}) => {
|
|
225
|
+
const isToday2 = isToday(date);
|
|
226
|
+
if (renderColumnHeader) {
|
|
227
|
+
return /* @__PURE__ */ jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale, resource }) });
|
|
228
|
+
}
|
|
229
|
+
if (resource) {
|
|
230
|
+
return /* @__PURE__ */ jsxs("div", { className: "grid place-items-center gap-1", children: [
|
|
231
|
+
resource.icon && /* @__PURE__ */ jsx("div", { className: "w-8 h-8 flex items-center justify-center", children: resource.icon }),
|
|
232
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: resource.name })
|
|
233
|
+
] });
|
|
234
|
+
}
|
|
235
|
+
return /* @__PURE__ */ jsxs("p", { className: "grid place-items-center", children: [
|
|
236
|
+
/* @__PURE__ */ jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
|
|
237
|
+
/* @__PURE__ */ jsx(
|
|
238
|
+
"span",
|
|
239
|
+
{
|
|
240
|
+
className: cn(
|
|
241
|
+
isToday2 && "bg-blue-500 rounded-full p-1 text-white"
|
|
242
|
+
),
|
|
243
|
+
children: date.getDate()
|
|
244
|
+
}
|
|
245
|
+
)
|
|
246
|
+
] });
|
|
247
|
+
};
|
|
230
248
|
function Calendar({
|
|
231
249
|
date = /* @__PURE__ */ new Date(),
|
|
232
250
|
events = [],
|
|
251
|
+
resources,
|
|
233
252
|
onEventClick,
|
|
234
253
|
onNewEvent,
|
|
235
254
|
onEventMove,
|
|
@@ -237,10 +256,12 @@ function Calendar({
|
|
|
237
256
|
onRemoveBlock,
|
|
238
257
|
config = {}
|
|
239
258
|
}) {
|
|
240
|
-
const { locale = "es-MX", icons = {} } = config;
|
|
259
|
+
const { locale = "es-MX", icons = {}, renderColumnHeader } = config;
|
|
241
260
|
const week = completeWeek(date);
|
|
242
261
|
const [activeId, setActiveId] = useState(null);
|
|
243
262
|
const { canMove } = useCalendarEvents(events);
|
|
263
|
+
const isResourceMode = !!resources && resources.length > 0;
|
|
264
|
+
const columnCount = isResourceMode ? resources.length : 7;
|
|
244
265
|
const sensors = useSensors(
|
|
245
266
|
useSensor(PointerSensor, {
|
|
246
267
|
activationConstraint: { distance: 8 }
|
|
@@ -255,10 +276,10 @@ function Calendar({
|
|
|
255
276
|
setActiveId(null);
|
|
256
277
|
if (!over) return;
|
|
257
278
|
const eventId = active.id.toString().replace("event-", "");
|
|
258
|
-
const [,
|
|
259
|
-
const
|
|
279
|
+
const [, colIndexStr, hourStr] = over.id.toString().split("-");
|
|
280
|
+
const colIndex = parseInt(colIndexStr);
|
|
260
281
|
const hour = parseInt(hourStr);
|
|
261
|
-
const targetDay = week[
|
|
282
|
+
const targetDay = isResourceMode ? date : week[colIndex];
|
|
262
283
|
const newStart = new Date(targetDay);
|
|
263
284
|
newStart.setHours(hour, 0, 0, 0);
|
|
264
285
|
const movedEvent = events.find((e) => e.id === eventId);
|
|
@@ -277,6 +298,24 @@ function Calendar({
|
|
|
277
298
|
setActiveId(null);
|
|
278
299
|
};
|
|
279
300
|
const activeEvent = activeId ? events.find((e) => `event-${e.id}` === activeId) : null;
|
|
301
|
+
const getColumnEvents = (colIndex) => {
|
|
302
|
+
if (isResourceMode) {
|
|
303
|
+
const resourceId = resources[colIndex].id;
|
|
304
|
+
return events.filter((event) => {
|
|
305
|
+
const eventDate = new Date(event.start);
|
|
306
|
+
return event.resourceId === resourceId && areSameDates(eventDate, date);
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
const dayOfWeek = week[colIndex];
|
|
310
|
+
return events.filter((event) => {
|
|
311
|
+
const eventDate = new Date(event.start);
|
|
312
|
+
return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
const gridStyle = {
|
|
316
|
+
display: "grid",
|
|
317
|
+
gridTemplateColumns: `auto repeat(${columnCount}, minmax(120px, 1fr))`
|
|
318
|
+
};
|
|
280
319
|
return /* @__PURE__ */ jsxs(
|
|
281
320
|
DndContext,
|
|
282
321
|
{
|
|
@@ -286,32 +325,66 @@ function Calendar({
|
|
|
286
325
|
onDragEnd: handleDragEnd,
|
|
287
326
|
onDragCancel: handleDragCancel,
|
|
288
327
|
children: [
|
|
289
|
-
/* @__PURE__ */ jsxs("article", { className: "w-full bg-white shadow rounded-xl", children: [
|
|
290
|
-
/* @__PURE__ */ jsxs(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
328
|
+
/* @__PURE__ */ jsxs("article", { className: "w-full bg-white shadow rounded-xl overflow-hidden", children: [
|
|
329
|
+
/* @__PURE__ */ jsxs(
|
|
330
|
+
"section",
|
|
331
|
+
{
|
|
332
|
+
style: gridStyle,
|
|
333
|
+
className: "place-items-center py-4 border-b",
|
|
334
|
+
children: [
|
|
335
|
+
/* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
|
|
336
|
+
isResourceMode ? resources.map((resource, index) => /* @__PURE__ */ jsx(
|
|
337
|
+
DayHeader,
|
|
338
|
+
{
|
|
339
|
+
date,
|
|
340
|
+
locale,
|
|
341
|
+
index,
|
|
342
|
+
resource,
|
|
343
|
+
renderColumnHeader
|
|
344
|
+
},
|
|
345
|
+
resource.id
|
|
346
|
+
)) : week.map((day, index) => /* @__PURE__ */ jsx(
|
|
347
|
+
DayHeader,
|
|
348
|
+
{
|
|
349
|
+
date: day,
|
|
350
|
+
locale,
|
|
351
|
+
index,
|
|
352
|
+
renderColumnHeader
|
|
353
|
+
},
|
|
354
|
+
day.toISOString()
|
|
355
|
+
))
|
|
356
|
+
]
|
|
357
|
+
}
|
|
358
|
+
),
|
|
359
|
+
/* @__PURE__ */ jsxs(
|
|
360
|
+
"section",
|
|
361
|
+
{
|
|
362
|
+
style: gridStyle,
|
|
363
|
+
className: cn(
|
|
364
|
+
"max-h-[80vh] overflow-y-auto",
|
|
365
|
+
isResourceMode && "overflow-x-auto"
|
|
366
|
+
),
|
|
367
|
+
children: [
|
|
368
|
+
/* @__PURE__ */ jsx(TimeColumn, {}),
|
|
369
|
+
Array.from({ length: columnCount }, (_, colIndex) => /* @__PURE__ */ jsx(
|
|
370
|
+
Column,
|
|
371
|
+
{
|
|
372
|
+
dayIndex: colIndex,
|
|
373
|
+
dayOfWeek: isResourceMode ? date : week[colIndex],
|
|
374
|
+
events: getColumnEvents(colIndex),
|
|
375
|
+
onNewEvent,
|
|
376
|
+
onAddBlock,
|
|
377
|
+
onRemoveBlock,
|
|
378
|
+
onEventClick,
|
|
379
|
+
locale,
|
|
380
|
+
icons,
|
|
381
|
+
resourceId: isResourceMode ? resources[colIndex].id : void 0
|
|
382
|
+
},
|
|
383
|
+
isResourceMode ? resources[colIndex].id : week[colIndex].toISOString()
|
|
384
|
+
))
|
|
385
|
+
]
|
|
386
|
+
}
|
|
387
|
+
)
|
|
315
388
|
] }),
|
|
316
389
|
/* @__PURE__ */ jsx(DragOverlay, { children: activeEvent ? /* @__PURE__ */ jsx(EventOverlay, { event: activeEvent }) : null })
|
|
317
390
|
]
|
|
@@ -409,6 +482,42 @@ var EmptyButton = ({
|
|
|
409
482
|
}
|
|
410
483
|
);
|
|
411
484
|
};
|
|
485
|
+
var calculateOverlapPositions = (events) => {
|
|
486
|
+
if (events.length === 0) return [];
|
|
487
|
+
const sorted = [...events].sort(
|
|
488
|
+
(a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()
|
|
489
|
+
);
|
|
490
|
+
const positions = /* @__PURE__ */ new Map();
|
|
491
|
+
const groups = [];
|
|
492
|
+
let currentGroup = [];
|
|
493
|
+
let groupEnd = 0;
|
|
494
|
+
for (const event of sorted) {
|
|
495
|
+
const eventStart = new Date(event.start);
|
|
496
|
+
const startHour = eventStart.getHours() + eventStart.getMinutes() / 60;
|
|
497
|
+
const endHour = startHour + event.duration / 60;
|
|
498
|
+
if (currentGroup.length === 0 || startHour < groupEnd) {
|
|
499
|
+
currentGroup.push(event);
|
|
500
|
+
groupEnd = Math.max(groupEnd, endHour);
|
|
501
|
+
} else {
|
|
502
|
+
groups.push(currentGroup);
|
|
503
|
+
currentGroup = [event];
|
|
504
|
+
groupEnd = endHour;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (currentGroup.length > 0) {
|
|
508
|
+
groups.push(currentGroup);
|
|
509
|
+
}
|
|
510
|
+
for (const group of groups) {
|
|
511
|
+
const totalColumns = group.length;
|
|
512
|
+
group.forEach((event, index) => {
|
|
513
|
+
positions.set(event.id, { column: index, totalColumns });
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
return sorted.map((event) => ({
|
|
517
|
+
event,
|
|
518
|
+
...positions.get(event.id)
|
|
519
|
+
}));
|
|
520
|
+
};
|
|
412
521
|
var Column = ({
|
|
413
522
|
onEventClick,
|
|
414
523
|
events = [],
|
|
@@ -418,9 +527,11 @@ var Column = ({
|
|
|
418
527
|
onRemoveBlock,
|
|
419
528
|
dayIndex,
|
|
420
529
|
locale,
|
|
421
|
-
icons
|
|
530
|
+
icons,
|
|
531
|
+
resourceId
|
|
422
532
|
}) => {
|
|
423
533
|
const columnRef = useRef(null);
|
|
534
|
+
const eventsWithPositions = calculateOverlapPositions(events);
|
|
424
535
|
useEffect(() => {
|
|
425
536
|
if (!columnRef.current) return;
|
|
426
537
|
const today = /* @__PURE__ */ new Date();
|
|
@@ -434,21 +545,24 @@ var Column = ({
|
|
|
434
545
|
}, 100);
|
|
435
546
|
}
|
|
436
547
|
}, [dayOfWeek]);
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
(event) => new Date(event.start).getHours() === hours
|
|
548
|
+
const findEventsAtHour = (hours) => {
|
|
549
|
+
const eventsStartingHere = eventsWithPositions.filter(
|
|
550
|
+
({ event }) => new Date(event.start).getHours() === hours
|
|
440
551
|
);
|
|
441
|
-
if (
|
|
442
|
-
return /* @__PURE__ */ jsx(
|
|
552
|
+
if (eventsStartingHere.length > 0) {
|
|
553
|
+
return eventsStartingHere.map(({ event, column, totalColumns }) => /* @__PURE__ */ jsx(
|
|
443
554
|
DraggableEvent,
|
|
444
555
|
{
|
|
445
|
-
onClick: () => onEventClick?.(
|
|
446
|
-
event
|
|
556
|
+
onClick: () => onEventClick?.(event),
|
|
557
|
+
event,
|
|
447
558
|
onRemoveBlock,
|
|
448
559
|
locale,
|
|
449
|
-
icons
|
|
450
|
-
|
|
451
|
-
|
|
560
|
+
icons,
|
|
561
|
+
overlapColumn: column,
|
|
562
|
+
overlapTotal: totalColumns
|
|
563
|
+
},
|
|
564
|
+
event.id
|
|
565
|
+
));
|
|
452
566
|
}
|
|
453
567
|
const eventSpansHere = events.find((event) => {
|
|
454
568
|
const eventStart = new Date(event.start);
|
|
@@ -474,7 +588,7 @@ var Column = ({
|
|
|
474
588
|
date: dayOfWeek,
|
|
475
589
|
className: "relative",
|
|
476
590
|
dayIndex,
|
|
477
|
-
children:
|
|
591
|
+
children: findEventsAtHour(hours)
|
|
478
592
|
},
|
|
479
593
|
hours
|
|
480
594
|
)) });
|
|
@@ -485,16 +599,22 @@ var DraggableEvent = ({
|
|
|
485
599
|
onClick,
|
|
486
600
|
onRemoveBlock,
|
|
487
601
|
locale,
|
|
488
|
-
icons
|
|
602
|
+
icons,
|
|
603
|
+
overlapColumn = 0,
|
|
604
|
+
overlapTotal = 1
|
|
489
605
|
}) => {
|
|
490
606
|
const [showOptions, setShowOptions] = useState(false);
|
|
491
607
|
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
|
492
608
|
id: `event-${event.id}`,
|
|
493
609
|
disabled: event.type === "BLOCK"
|
|
494
610
|
});
|
|
611
|
+
const widthPercent = event.type === "BLOCK" ? 100 : 90 / overlapTotal;
|
|
612
|
+
const leftPercent = event.type === "BLOCK" ? 0 : overlapColumn * (90 / overlapTotal);
|
|
495
613
|
const style = {
|
|
496
614
|
height: event.duration / 60 * 64,
|
|
497
|
-
transform: transform ? CSS.Translate.toString(transform) : void 0
|
|
615
|
+
transform: transform ? CSS.Translate.toString(transform) : void 0,
|
|
616
|
+
width: `${widthPercent}%`,
|
|
617
|
+
left: `${leftPercent}%`
|
|
498
618
|
};
|
|
499
619
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
500
620
|
/* @__PURE__ */ jsxs(
|
|
@@ -507,7 +627,7 @@ var DraggableEvent = ({
|
|
|
507
627
|
...attributes,
|
|
508
628
|
className: cn(
|
|
509
629
|
"border grid gap-y-1 overflow-hidden place-content-start",
|
|
510
|
-
"text-xs text-left pl-1 absolute top-0
|
|
630
|
+
"text-xs text-left pl-1 absolute top-0 bg-blue-500 text-white rounded-md z-10",
|
|
511
631
|
event.type === "BLOCK" && "bg-gray-300 h-full w-full text-center cursor-not-allowed relative p-0",
|
|
512
632
|
event.type !== "BLOCK" && "cursor-grab",
|
|
513
633
|
isDragging && event.type !== "BLOCK" && "cursor-grabbing opacity-50"
|
|
@@ -605,5 +725,132 @@ var Options = ({
|
|
|
605
725
|
}
|
|
606
726
|
);
|
|
607
727
|
};
|
|
728
|
+
var DefaultPrevIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) });
|
|
729
|
+
var DefaultNextIcon = () => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }) });
|
|
730
|
+
function CalendarControls({
|
|
731
|
+
controls,
|
|
732
|
+
todayLabel = "HOY",
|
|
733
|
+
weekLabel = "SEMANA",
|
|
734
|
+
dayLabel = "D\xCDA",
|
|
735
|
+
showViewToggle = true,
|
|
736
|
+
prevIcon,
|
|
737
|
+
nextIcon,
|
|
738
|
+
actions,
|
|
739
|
+
className = ""
|
|
740
|
+
}) {
|
|
741
|
+
const { label, goToToday, goToPrev, goToNext, view, toggleView, isToday: isToday2 } = controls;
|
|
742
|
+
return /* @__PURE__ */ jsxs(
|
|
743
|
+
"div",
|
|
744
|
+
{
|
|
745
|
+
className: `flex items-center justify-between gap-4 py-3 ${className}`,
|
|
746
|
+
children: [
|
|
747
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
748
|
+
/* @__PURE__ */ jsx(
|
|
749
|
+
"button",
|
|
750
|
+
{
|
|
751
|
+
onClick: goToToday,
|
|
752
|
+
disabled: isToday2,
|
|
753
|
+
className: `px-4 py-2 text-sm font-medium rounded-full border transition-colors ${isToday2 ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "bg-blue-500 text-white hover:bg-blue-600"}`,
|
|
754
|
+
children: todayLabel
|
|
755
|
+
}
|
|
756
|
+
),
|
|
757
|
+
/* @__PURE__ */ jsx(
|
|
758
|
+
"button",
|
|
759
|
+
{
|
|
760
|
+
onClick: goToPrev,
|
|
761
|
+
className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
|
|
762
|
+
"aria-label": "Previous",
|
|
763
|
+
children: prevIcon ?? /* @__PURE__ */ jsx(DefaultPrevIcon, {})
|
|
764
|
+
}
|
|
765
|
+
),
|
|
766
|
+
/* @__PURE__ */ jsx(
|
|
767
|
+
"button",
|
|
768
|
+
{
|
|
769
|
+
onClick: goToNext,
|
|
770
|
+
className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
|
|
771
|
+
"aria-label": "Next",
|
|
772
|
+
children: nextIcon ?? /* @__PURE__ */ jsx(DefaultNextIcon, {})
|
|
773
|
+
}
|
|
774
|
+
),
|
|
775
|
+
/* @__PURE__ */ jsx("span", { className: "text-lg font-medium capitalize ml-2", children: label })
|
|
776
|
+
] }),
|
|
777
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
778
|
+
showViewToggle && /* @__PURE__ */ jsxs(
|
|
779
|
+
"select",
|
|
780
|
+
{
|
|
781
|
+
value: view,
|
|
782
|
+
onChange: toggleView,
|
|
783
|
+
className: "px-4 py-2 text-sm font-medium border rounded-lg bg-white hover:bg-gray-50 cursor-pointer",
|
|
784
|
+
children: [
|
|
785
|
+
/* @__PURE__ */ jsx("option", { value: "week", children: weekLabel }),
|
|
786
|
+
/* @__PURE__ */ jsx("option", { value: "day", children: dayLabel })
|
|
787
|
+
]
|
|
788
|
+
}
|
|
789
|
+
),
|
|
790
|
+
actions
|
|
791
|
+
] })
|
|
792
|
+
]
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
function useCalendarControls(options = {}) {
|
|
797
|
+
const {
|
|
798
|
+
initialDate = /* @__PURE__ */ new Date(),
|
|
799
|
+
initialView = "week",
|
|
800
|
+
locale = "es-MX"
|
|
801
|
+
} = options;
|
|
802
|
+
const [date, setDate] = useState(initialDate);
|
|
803
|
+
const [view, setView] = useState(initialView);
|
|
804
|
+
const week = useMemo(() => completeWeek(date), [date]);
|
|
805
|
+
const goToToday = useCallback(() => {
|
|
806
|
+
setDate(/* @__PURE__ */ new Date());
|
|
807
|
+
}, []);
|
|
808
|
+
const goToPrev = useCallback(() => {
|
|
809
|
+
setDate((d) => addDaysToDate(d, view === "week" ? -7 : -1));
|
|
810
|
+
}, [view]);
|
|
811
|
+
const goToNext = useCallback(() => {
|
|
812
|
+
setDate((d) => addDaysToDate(d, view === "week" ? 7 : 1));
|
|
813
|
+
}, [view]);
|
|
814
|
+
const toggleView = useCallback(() => {
|
|
815
|
+
setView((v) => v === "week" ? "day" : "week");
|
|
816
|
+
}, []);
|
|
817
|
+
const isToday2 = useMemo(() => {
|
|
818
|
+
const today = /* @__PURE__ */ new Date();
|
|
819
|
+
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
|
|
820
|
+
}, [date]);
|
|
821
|
+
const label = useMemo(() => {
|
|
822
|
+
if (view === "week") {
|
|
823
|
+
const monthName = week[0].toLocaleDateString(locale, { month: "long" });
|
|
824
|
+
const year = week[0].getFullYear();
|
|
825
|
+
const endMonth = week[6].getMonth();
|
|
826
|
+
if (week[0].getMonth() !== endMonth) {
|
|
827
|
+
const endMonthName = week[6].toLocaleDateString(locale, {
|
|
828
|
+
month: "short"
|
|
829
|
+
});
|
|
830
|
+
return `${week[0].getDate()} ${week[0].toLocaleDateString(locale, { month: "short" })} - ${week[6].getDate()} ${endMonthName} ${year}`;
|
|
831
|
+
}
|
|
832
|
+
return `${week[0].getDate()} - ${week[6].getDate()} ${monthName} ${year}`;
|
|
833
|
+
}
|
|
834
|
+
return date.toLocaleDateString(locale, {
|
|
835
|
+
weekday: "long",
|
|
836
|
+
day: "numeric",
|
|
837
|
+
month: "long",
|
|
838
|
+
year: "numeric"
|
|
839
|
+
});
|
|
840
|
+
}, [view, week, date, locale]);
|
|
841
|
+
return {
|
|
842
|
+
date,
|
|
843
|
+
view,
|
|
844
|
+
week,
|
|
845
|
+
label,
|
|
846
|
+
goToToday,
|
|
847
|
+
goToPrev,
|
|
848
|
+
goToNext,
|
|
849
|
+
toggleView,
|
|
850
|
+
setDate,
|
|
851
|
+
setView,
|
|
852
|
+
isToday: isToday2
|
|
853
|
+
};
|
|
854
|
+
}
|
|
608
855
|
|
|
609
|
-
export { Calendar, Calendar as SimpleBigWeekView, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarEvents, useClickOutside, useEventOverlap };
|
|
856
|
+
export { Calendar, CalendarControls, Calendar as SimpleBigWeekView, addDaysToDate, addMinutesToDate, areSameDates, completeWeek, formatDate, fromDateToTimeString, fromMinsToLocaleTimeString, fromMinsToTimeString, generateHours, getDaysInMonth, getMonday, isToday, useCalendarControls, useCalendarEvents, useClickOutside, useEventOverlap };
|