@hectorbliss/denik-calendar 0.0.3 → 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 +129 -93
- package/dist/index.cjs +283 -54
- package/dist/index.d.cts +116 -3
- package/dist/index.d.ts +116 -3
- package/dist/index.js +283 -56
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
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
|
+
}
|
|
4
16
|
/**
|
|
5
17
|
* Props passed to custom column header renderer
|
|
6
18
|
* Use this to build custom headers for resources (courts, rooms, employees, etc.)
|
|
@@ -8,12 +20,14 @@ import { ReactNode, RefObject } from 'react';
|
|
|
8
20
|
interface ColumnHeaderProps {
|
|
9
21
|
/** The date for this column */
|
|
10
22
|
date: Date;
|
|
11
|
-
/** Column index
|
|
23
|
+
/** Column index */
|
|
12
24
|
index: number;
|
|
13
25
|
/** Whether this column represents today */
|
|
14
26
|
isToday: boolean;
|
|
15
27
|
/** The configured locale */
|
|
16
28
|
locale: string;
|
|
29
|
+
/** Resource data (only in resource mode) */
|
|
30
|
+
resource?: Resource;
|
|
17
31
|
}
|
|
18
32
|
/**
|
|
19
33
|
* Generic calendar event - decoupled from any ORM
|
|
@@ -27,6 +41,8 @@ interface CalendarEvent {
|
|
|
27
41
|
service?: {
|
|
28
42
|
name: string;
|
|
29
43
|
} | null;
|
|
44
|
+
/** Resource ID for day/resource view (court, room, etc.) */
|
|
45
|
+
resourceId?: string;
|
|
30
46
|
}
|
|
31
47
|
/**
|
|
32
48
|
* Calendar configuration options
|
|
@@ -70,6 +86,18 @@ interface CalendarProps {
|
|
|
70
86
|
date?: Date;
|
|
71
87
|
/** Array of events to display */
|
|
72
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[];
|
|
73
101
|
/** Callback when an event is clicked */
|
|
74
102
|
onEventClick?: (event: CalendarEvent) => void;
|
|
75
103
|
/** Callback when an event is moved via drag & drop */
|
|
@@ -84,7 +112,92 @@ interface CalendarProps {
|
|
|
84
112
|
config?: CalendarConfig;
|
|
85
113
|
}
|
|
86
114
|
|
|
87
|
-
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;
|
|
88
201
|
|
|
89
202
|
/**
|
|
90
203
|
* Hook for managing calendar events - overlap detection, filtering, and availability
|
|
@@ -159,4 +272,4 @@ declare function useClickOutside<T extends HTMLElement>({ isActive, onOutsideCli
|
|
|
159
272
|
*/
|
|
160
273
|
declare function formatDate(date: Date, locale?: string): string;
|
|
161
274
|
|
|
162
|
-
export { Calendar, type CalendarConfig, type CalendarEvent, type CalendarProps, type ColumnHeaderProps, 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';
|
|
@@ -219,11 +219,18 @@ var DayHeader = ({
|
|
|
219
219
|
date,
|
|
220
220
|
locale,
|
|
221
221
|
index,
|
|
222
|
+
resource,
|
|
222
223
|
renderColumnHeader
|
|
223
224
|
}) => {
|
|
224
225
|
const isToday2 = isToday(date);
|
|
225
226
|
if (renderColumnHeader) {
|
|
226
|
-
return /* @__PURE__ */ jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale }) });
|
|
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
|
+
] });
|
|
227
234
|
}
|
|
228
235
|
return /* @__PURE__ */ jsxs("p", { className: "grid place-items-center", children: [
|
|
229
236
|
/* @__PURE__ */ jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
|
|
@@ -241,6 +248,7 @@ var DayHeader = ({
|
|
|
241
248
|
function Calendar({
|
|
242
249
|
date = /* @__PURE__ */ new Date(),
|
|
243
250
|
events = [],
|
|
251
|
+
resources,
|
|
244
252
|
onEventClick,
|
|
245
253
|
onNewEvent,
|
|
246
254
|
onEventMove,
|
|
@@ -252,6 +260,8 @@ function Calendar({
|
|
|
252
260
|
const week = completeWeek(date);
|
|
253
261
|
const [activeId, setActiveId] = useState(null);
|
|
254
262
|
const { canMove } = useCalendarEvents(events);
|
|
263
|
+
const isResourceMode = !!resources && resources.length > 0;
|
|
264
|
+
const columnCount = isResourceMode ? resources.length : 7;
|
|
255
265
|
const sensors = useSensors(
|
|
256
266
|
useSensor(PointerSensor, {
|
|
257
267
|
activationConstraint: { distance: 8 }
|
|
@@ -266,10 +276,10 @@ function Calendar({
|
|
|
266
276
|
setActiveId(null);
|
|
267
277
|
if (!over) return;
|
|
268
278
|
const eventId = active.id.toString().replace("event-", "");
|
|
269
|
-
const [,
|
|
270
|
-
const
|
|
279
|
+
const [, colIndexStr, hourStr] = over.id.toString().split("-");
|
|
280
|
+
const colIndex = parseInt(colIndexStr);
|
|
271
281
|
const hour = parseInt(hourStr);
|
|
272
|
-
const targetDay = week[
|
|
282
|
+
const targetDay = isResourceMode ? date : week[colIndex];
|
|
273
283
|
const newStart = new Date(targetDay);
|
|
274
284
|
newStart.setHours(hour, 0, 0, 0);
|
|
275
285
|
const movedEvent = events.find((e) => e.id === eventId);
|
|
@@ -288,6 +298,24 @@ function Calendar({
|
|
|
288
298
|
setActiveId(null);
|
|
289
299
|
};
|
|
290
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
|
+
};
|
|
291
319
|
return /* @__PURE__ */ jsxs(
|
|
292
320
|
DndContext,
|
|
293
321
|
{
|
|
@@ -297,41 +325,66 @@ function Calendar({
|
|
|
297
325
|
onDragEnd: handleDragEnd,
|
|
298
326
|
onDragCancel: handleDragCancel,
|
|
299
327
|
children: [
|
|
300
|
-
/* @__PURE__ */ jsxs("article", { className: "w-full bg-white shadow rounded-xl", children: [
|
|
301
|
-
/* @__PURE__ */ jsxs(
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
index
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
+
)
|
|
335
388
|
] }),
|
|
336
389
|
/* @__PURE__ */ jsx(DragOverlay, { children: activeEvent ? /* @__PURE__ */ jsx(EventOverlay, { event: activeEvent }) : null })
|
|
337
390
|
]
|
|
@@ -429,6 +482,42 @@ var EmptyButton = ({
|
|
|
429
482
|
}
|
|
430
483
|
);
|
|
431
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
|
+
};
|
|
432
521
|
var Column = ({
|
|
433
522
|
onEventClick,
|
|
434
523
|
events = [],
|
|
@@ -438,9 +527,11 @@ var Column = ({
|
|
|
438
527
|
onRemoveBlock,
|
|
439
528
|
dayIndex,
|
|
440
529
|
locale,
|
|
441
|
-
icons
|
|
530
|
+
icons,
|
|
531
|
+
resourceId
|
|
442
532
|
}) => {
|
|
443
533
|
const columnRef = useRef(null);
|
|
534
|
+
const eventsWithPositions = calculateOverlapPositions(events);
|
|
444
535
|
useEffect(() => {
|
|
445
536
|
if (!columnRef.current) return;
|
|
446
537
|
const today = /* @__PURE__ */ new Date();
|
|
@@ -454,21 +545,24 @@ var Column = ({
|
|
|
454
545
|
}, 100);
|
|
455
546
|
}
|
|
456
547
|
}, [dayOfWeek]);
|
|
457
|
-
const
|
|
458
|
-
const
|
|
459
|
-
(event) => new Date(event.start).getHours() === hours
|
|
548
|
+
const findEventsAtHour = (hours) => {
|
|
549
|
+
const eventsStartingHere = eventsWithPositions.filter(
|
|
550
|
+
({ event }) => new Date(event.start).getHours() === hours
|
|
460
551
|
);
|
|
461
|
-
if (
|
|
462
|
-
return /* @__PURE__ */ jsx(
|
|
552
|
+
if (eventsStartingHere.length > 0) {
|
|
553
|
+
return eventsStartingHere.map(({ event, column, totalColumns }) => /* @__PURE__ */ jsx(
|
|
463
554
|
DraggableEvent,
|
|
464
555
|
{
|
|
465
|
-
onClick: () => onEventClick?.(
|
|
466
|
-
event
|
|
556
|
+
onClick: () => onEventClick?.(event),
|
|
557
|
+
event,
|
|
467
558
|
onRemoveBlock,
|
|
468
559
|
locale,
|
|
469
|
-
icons
|
|
470
|
-
|
|
471
|
-
|
|
560
|
+
icons,
|
|
561
|
+
overlapColumn: column,
|
|
562
|
+
overlapTotal: totalColumns
|
|
563
|
+
},
|
|
564
|
+
event.id
|
|
565
|
+
));
|
|
472
566
|
}
|
|
473
567
|
const eventSpansHere = events.find((event) => {
|
|
474
568
|
const eventStart = new Date(event.start);
|
|
@@ -494,7 +588,7 @@ var Column = ({
|
|
|
494
588
|
date: dayOfWeek,
|
|
495
589
|
className: "relative",
|
|
496
590
|
dayIndex,
|
|
497
|
-
children:
|
|
591
|
+
children: findEventsAtHour(hours)
|
|
498
592
|
},
|
|
499
593
|
hours
|
|
500
594
|
)) });
|
|
@@ -505,16 +599,22 @@ var DraggableEvent = ({
|
|
|
505
599
|
onClick,
|
|
506
600
|
onRemoveBlock,
|
|
507
601
|
locale,
|
|
508
|
-
icons
|
|
602
|
+
icons,
|
|
603
|
+
overlapColumn = 0,
|
|
604
|
+
overlapTotal = 1
|
|
509
605
|
}) => {
|
|
510
606
|
const [showOptions, setShowOptions] = useState(false);
|
|
511
607
|
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
|
512
608
|
id: `event-${event.id}`,
|
|
513
609
|
disabled: event.type === "BLOCK"
|
|
514
610
|
});
|
|
611
|
+
const widthPercent = event.type === "BLOCK" ? 100 : 90 / overlapTotal;
|
|
612
|
+
const leftPercent = event.type === "BLOCK" ? 0 : overlapColumn * (90 / overlapTotal);
|
|
515
613
|
const style = {
|
|
516
614
|
height: event.duration / 60 * 64,
|
|
517
|
-
transform: transform ? CSS.Translate.toString(transform) : void 0
|
|
615
|
+
transform: transform ? CSS.Translate.toString(transform) : void 0,
|
|
616
|
+
width: `${widthPercent}%`,
|
|
617
|
+
left: `${leftPercent}%`
|
|
518
618
|
};
|
|
519
619
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
520
620
|
/* @__PURE__ */ jsxs(
|
|
@@ -527,7 +627,7 @@ var DraggableEvent = ({
|
|
|
527
627
|
...attributes,
|
|
528
628
|
className: cn(
|
|
529
629
|
"border grid gap-y-1 overflow-hidden place-content-start",
|
|
530
|
-
"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",
|
|
531
631
|
event.type === "BLOCK" && "bg-gray-300 h-full w-full text-center cursor-not-allowed relative p-0",
|
|
532
632
|
event.type !== "BLOCK" && "cursor-grab",
|
|
533
633
|
isDragging && event.type !== "BLOCK" && "cursor-grabbing opacity-50"
|
|
@@ -625,5 +725,132 @@ var Options = ({
|
|
|
625
725
|
}
|
|
626
726
|
);
|
|
627
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
|
+
}
|
|
628
855
|
|
|
629
|
-
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 };
|