@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.cjs
CHANGED
|
@@ -221,11 +221,18 @@ var DayHeader = ({
|
|
|
221
221
|
date,
|
|
222
222
|
locale,
|
|
223
223
|
index,
|
|
224
|
+
resource,
|
|
224
225
|
renderColumnHeader
|
|
225
226
|
}) => {
|
|
226
227
|
const isToday2 = isToday(date);
|
|
227
228
|
if (renderColumnHeader) {
|
|
228
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale }) });
|
|
229
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid place-items-center", children: renderColumnHeader({ date, index, isToday: isToday2, locale, resource }) });
|
|
230
|
+
}
|
|
231
|
+
if (resource) {
|
|
232
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid place-items-center gap-1", children: [
|
|
233
|
+
resource.icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-8 h-8 flex items-center justify-center", children: resource.icon }),
|
|
234
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: resource.name })
|
|
235
|
+
] });
|
|
229
236
|
}
|
|
230
237
|
return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "grid place-items-center", children: [
|
|
231
238
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
|
|
@@ -243,6 +250,7 @@ var DayHeader = ({
|
|
|
243
250
|
function Calendar({
|
|
244
251
|
date = /* @__PURE__ */ new Date(),
|
|
245
252
|
events = [],
|
|
253
|
+
resources,
|
|
246
254
|
onEventClick,
|
|
247
255
|
onNewEvent,
|
|
248
256
|
onEventMove,
|
|
@@ -254,6 +262,8 @@ function Calendar({
|
|
|
254
262
|
const week = completeWeek(date);
|
|
255
263
|
const [activeId, setActiveId] = react.useState(null);
|
|
256
264
|
const { canMove } = useCalendarEvents(events);
|
|
265
|
+
const isResourceMode = !!resources && resources.length > 0;
|
|
266
|
+
const columnCount = isResourceMode ? resources.length : 7;
|
|
257
267
|
const sensors = core.useSensors(
|
|
258
268
|
core.useSensor(core.PointerSensor, {
|
|
259
269
|
activationConstraint: { distance: 8 }
|
|
@@ -268,10 +278,10 @@ function Calendar({
|
|
|
268
278
|
setActiveId(null);
|
|
269
279
|
if (!over) return;
|
|
270
280
|
const eventId = active.id.toString().replace("event-", "");
|
|
271
|
-
const [,
|
|
272
|
-
const
|
|
281
|
+
const [, colIndexStr, hourStr] = over.id.toString().split("-");
|
|
282
|
+
const colIndex = parseInt(colIndexStr);
|
|
273
283
|
const hour = parseInt(hourStr);
|
|
274
|
-
const targetDay = week[
|
|
284
|
+
const targetDay = isResourceMode ? date : week[colIndex];
|
|
275
285
|
const newStart = new Date(targetDay);
|
|
276
286
|
newStart.setHours(hour, 0, 0, 0);
|
|
277
287
|
const movedEvent = events.find((e) => e.id === eventId);
|
|
@@ -290,6 +300,24 @@ function Calendar({
|
|
|
290
300
|
setActiveId(null);
|
|
291
301
|
};
|
|
292
302
|
const activeEvent = activeId ? events.find((e) => `event-${e.id}` === activeId) : null;
|
|
303
|
+
const getColumnEvents = (colIndex) => {
|
|
304
|
+
if (isResourceMode) {
|
|
305
|
+
const resourceId = resources[colIndex].id;
|
|
306
|
+
return events.filter((event) => {
|
|
307
|
+
const eventDate = new Date(event.start);
|
|
308
|
+
return event.resourceId === resourceId && areSameDates(eventDate, date);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
const dayOfWeek = week[colIndex];
|
|
312
|
+
return events.filter((event) => {
|
|
313
|
+
const eventDate = new Date(event.start);
|
|
314
|
+
return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
const gridStyle = {
|
|
318
|
+
display: "grid",
|
|
319
|
+
gridTemplateColumns: `auto repeat(${columnCount}, minmax(120px, 1fr))`
|
|
320
|
+
};
|
|
293
321
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
294
322
|
core.DndContext,
|
|
295
323
|
{
|
|
@@ -299,41 +327,66 @@ function Calendar({
|
|
|
299
327
|
onDragEnd: handleDragEnd,
|
|
300
328
|
onDragCancel: handleDragCancel,
|
|
301
329
|
children: [
|
|
302
|
-
/* @__PURE__ */ jsxRuntime.jsxs("article", { className: "w-full bg-white shadow rounded-xl", children: [
|
|
303
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
index
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
330
|
+
/* @__PURE__ */ jsxRuntime.jsxs("article", { className: "w-full bg-white shadow rounded-xl overflow-hidden", children: [
|
|
331
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
332
|
+
"section",
|
|
333
|
+
{
|
|
334
|
+
style: gridStyle,
|
|
335
|
+
className: "place-items-center py-4 border-b",
|
|
336
|
+
children: [
|
|
337
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
|
|
338
|
+
isResourceMode ? resources.map((resource, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
339
|
+
DayHeader,
|
|
340
|
+
{
|
|
341
|
+
date,
|
|
342
|
+
locale,
|
|
343
|
+
index,
|
|
344
|
+
resource,
|
|
345
|
+
renderColumnHeader
|
|
346
|
+
},
|
|
347
|
+
resource.id
|
|
348
|
+
)) : week.map((day, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
349
|
+
DayHeader,
|
|
350
|
+
{
|
|
351
|
+
date: day,
|
|
352
|
+
locale,
|
|
353
|
+
index,
|
|
354
|
+
renderColumnHeader
|
|
355
|
+
},
|
|
356
|
+
day.toISOString()
|
|
357
|
+
))
|
|
358
|
+
]
|
|
359
|
+
}
|
|
360
|
+
),
|
|
361
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
362
|
+
"section",
|
|
363
|
+
{
|
|
364
|
+
style: gridStyle,
|
|
365
|
+
className: cn(
|
|
366
|
+
"max-h-[80vh] overflow-y-auto",
|
|
367
|
+
isResourceMode && "overflow-x-auto"
|
|
368
|
+
),
|
|
369
|
+
children: [
|
|
370
|
+
/* @__PURE__ */ jsxRuntime.jsx(TimeColumn, {}),
|
|
371
|
+
Array.from({ length: columnCount }, (_, colIndex) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
372
|
+
Column,
|
|
373
|
+
{
|
|
374
|
+
dayIndex: colIndex,
|
|
375
|
+
dayOfWeek: isResourceMode ? date : week[colIndex],
|
|
376
|
+
events: getColumnEvents(colIndex),
|
|
377
|
+
onNewEvent,
|
|
378
|
+
onAddBlock,
|
|
379
|
+
onRemoveBlock,
|
|
380
|
+
onEventClick,
|
|
381
|
+
locale,
|
|
382
|
+
icons,
|
|
383
|
+
resourceId: isResourceMode ? resources[colIndex].id : void 0
|
|
384
|
+
},
|
|
385
|
+
isResourceMode ? resources[colIndex].id : week[colIndex].toISOString()
|
|
386
|
+
))
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
)
|
|
337
390
|
] }),
|
|
338
391
|
/* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { children: activeEvent ? /* @__PURE__ */ jsxRuntime.jsx(EventOverlay, { event: activeEvent }) : null })
|
|
339
392
|
]
|
|
@@ -431,6 +484,42 @@ var EmptyButton = ({
|
|
|
431
484
|
}
|
|
432
485
|
);
|
|
433
486
|
};
|
|
487
|
+
var calculateOverlapPositions = (events) => {
|
|
488
|
+
if (events.length === 0) return [];
|
|
489
|
+
const sorted = [...events].sort(
|
|
490
|
+
(a, b) => new Date(a.start).getTime() - new Date(b.start).getTime()
|
|
491
|
+
);
|
|
492
|
+
const positions = /* @__PURE__ */ new Map();
|
|
493
|
+
const groups = [];
|
|
494
|
+
let currentGroup = [];
|
|
495
|
+
let groupEnd = 0;
|
|
496
|
+
for (const event of sorted) {
|
|
497
|
+
const eventStart = new Date(event.start);
|
|
498
|
+
const startHour = eventStart.getHours() + eventStart.getMinutes() / 60;
|
|
499
|
+
const endHour = startHour + event.duration / 60;
|
|
500
|
+
if (currentGroup.length === 0 || startHour < groupEnd) {
|
|
501
|
+
currentGroup.push(event);
|
|
502
|
+
groupEnd = Math.max(groupEnd, endHour);
|
|
503
|
+
} else {
|
|
504
|
+
groups.push(currentGroup);
|
|
505
|
+
currentGroup = [event];
|
|
506
|
+
groupEnd = endHour;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (currentGroup.length > 0) {
|
|
510
|
+
groups.push(currentGroup);
|
|
511
|
+
}
|
|
512
|
+
for (const group of groups) {
|
|
513
|
+
const totalColumns = group.length;
|
|
514
|
+
group.forEach((event, index) => {
|
|
515
|
+
positions.set(event.id, { column: index, totalColumns });
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
return sorted.map((event) => ({
|
|
519
|
+
event,
|
|
520
|
+
...positions.get(event.id)
|
|
521
|
+
}));
|
|
522
|
+
};
|
|
434
523
|
var Column = ({
|
|
435
524
|
onEventClick,
|
|
436
525
|
events = [],
|
|
@@ -440,9 +529,11 @@ var Column = ({
|
|
|
440
529
|
onRemoveBlock,
|
|
441
530
|
dayIndex,
|
|
442
531
|
locale,
|
|
443
|
-
icons
|
|
532
|
+
icons,
|
|
533
|
+
resourceId
|
|
444
534
|
}) => {
|
|
445
535
|
const columnRef = react.useRef(null);
|
|
536
|
+
const eventsWithPositions = calculateOverlapPositions(events);
|
|
446
537
|
react.useEffect(() => {
|
|
447
538
|
if (!columnRef.current) return;
|
|
448
539
|
const today = /* @__PURE__ */ new Date();
|
|
@@ -456,21 +547,24 @@ var Column = ({
|
|
|
456
547
|
}, 100);
|
|
457
548
|
}
|
|
458
549
|
}, [dayOfWeek]);
|
|
459
|
-
const
|
|
460
|
-
const
|
|
461
|
-
(event) => new Date(event.start).getHours() === hours
|
|
550
|
+
const findEventsAtHour = (hours) => {
|
|
551
|
+
const eventsStartingHere = eventsWithPositions.filter(
|
|
552
|
+
({ event }) => new Date(event.start).getHours() === hours
|
|
462
553
|
);
|
|
463
|
-
if (
|
|
464
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
554
|
+
if (eventsStartingHere.length > 0) {
|
|
555
|
+
return eventsStartingHere.map(({ event, column, totalColumns }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
465
556
|
DraggableEvent,
|
|
466
557
|
{
|
|
467
|
-
onClick: () => onEventClick?.(
|
|
468
|
-
event
|
|
558
|
+
onClick: () => onEventClick?.(event),
|
|
559
|
+
event,
|
|
469
560
|
onRemoveBlock,
|
|
470
561
|
locale,
|
|
471
|
-
icons
|
|
472
|
-
|
|
473
|
-
|
|
562
|
+
icons,
|
|
563
|
+
overlapColumn: column,
|
|
564
|
+
overlapTotal: totalColumns
|
|
565
|
+
},
|
|
566
|
+
event.id
|
|
567
|
+
));
|
|
474
568
|
}
|
|
475
569
|
const eventSpansHere = events.find((event) => {
|
|
476
570
|
const eventStart = new Date(event.start);
|
|
@@ -496,7 +590,7 @@ var Column = ({
|
|
|
496
590
|
date: dayOfWeek,
|
|
497
591
|
className: "relative",
|
|
498
592
|
dayIndex,
|
|
499
|
-
children:
|
|
593
|
+
children: findEventsAtHour(hours)
|
|
500
594
|
},
|
|
501
595
|
hours
|
|
502
596
|
)) });
|
|
@@ -507,16 +601,22 @@ var DraggableEvent = ({
|
|
|
507
601
|
onClick,
|
|
508
602
|
onRemoveBlock,
|
|
509
603
|
locale,
|
|
510
|
-
icons
|
|
604
|
+
icons,
|
|
605
|
+
overlapColumn = 0,
|
|
606
|
+
overlapTotal = 1
|
|
511
607
|
}) => {
|
|
512
608
|
const [showOptions, setShowOptions] = react.useState(false);
|
|
513
609
|
const { attributes, listeners, setNodeRef, transform, isDragging } = core.useDraggable({
|
|
514
610
|
id: `event-${event.id}`,
|
|
515
611
|
disabled: event.type === "BLOCK"
|
|
516
612
|
});
|
|
613
|
+
const widthPercent = event.type === "BLOCK" ? 100 : 90 / overlapTotal;
|
|
614
|
+
const leftPercent = event.type === "BLOCK" ? 0 : overlapColumn * (90 / overlapTotal);
|
|
517
615
|
const style = {
|
|
518
616
|
height: event.duration / 60 * 64,
|
|
519
|
-
transform: transform ? utilities.CSS.Translate.toString(transform) : void 0
|
|
617
|
+
transform: transform ? utilities.CSS.Translate.toString(transform) : void 0,
|
|
618
|
+
width: `${widthPercent}%`,
|
|
619
|
+
left: `${leftPercent}%`
|
|
520
620
|
};
|
|
521
621
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
522
622
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -529,7 +629,7 @@ var DraggableEvent = ({
|
|
|
529
629
|
...attributes,
|
|
530
630
|
className: cn(
|
|
531
631
|
"border grid gap-y-1 overflow-hidden place-content-start",
|
|
532
|
-
"text-xs text-left pl-1 absolute top-0
|
|
632
|
+
"text-xs text-left pl-1 absolute top-0 bg-blue-500 text-white rounded-md z-10",
|
|
533
633
|
event.type === "BLOCK" && "bg-gray-300 h-full w-full text-center cursor-not-allowed relative p-0",
|
|
534
634
|
event.type !== "BLOCK" && "cursor-grab",
|
|
535
635
|
isDragging && event.type !== "BLOCK" && "cursor-grabbing opacity-50"
|
|
@@ -627,8 +727,136 @@ var Options = ({
|
|
|
627
727
|
}
|
|
628
728
|
);
|
|
629
729
|
};
|
|
730
|
+
var DefaultPrevIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) });
|
|
731
|
+
var DefaultNextIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-5 h-5", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" }) });
|
|
732
|
+
function CalendarControls({
|
|
733
|
+
controls,
|
|
734
|
+
todayLabel = "HOY",
|
|
735
|
+
weekLabel = "SEMANA",
|
|
736
|
+
dayLabel = "D\xCDA",
|
|
737
|
+
showViewToggle = true,
|
|
738
|
+
prevIcon,
|
|
739
|
+
nextIcon,
|
|
740
|
+
actions,
|
|
741
|
+
className = ""
|
|
742
|
+
}) {
|
|
743
|
+
const { label, goToToday, goToPrev, goToNext, view, toggleView, isToday: isToday2 } = controls;
|
|
744
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
745
|
+
"div",
|
|
746
|
+
{
|
|
747
|
+
className: `flex items-center justify-between gap-4 py-3 ${className}`,
|
|
748
|
+
children: [
|
|
749
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
750
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
751
|
+
"button",
|
|
752
|
+
{
|
|
753
|
+
onClick: goToToday,
|
|
754
|
+
disabled: isToday2,
|
|
755
|
+
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"}`,
|
|
756
|
+
children: todayLabel
|
|
757
|
+
}
|
|
758
|
+
),
|
|
759
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
760
|
+
"button",
|
|
761
|
+
{
|
|
762
|
+
onClick: goToPrev,
|
|
763
|
+
className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
|
|
764
|
+
"aria-label": "Previous",
|
|
765
|
+
children: prevIcon ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultPrevIcon, {})
|
|
766
|
+
}
|
|
767
|
+
),
|
|
768
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
769
|
+
"button",
|
|
770
|
+
{
|
|
771
|
+
onClick: goToNext,
|
|
772
|
+
className: "p-2 rounded-full hover:bg-gray-100 transition-colors",
|
|
773
|
+
"aria-label": "Next",
|
|
774
|
+
children: nextIcon ?? /* @__PURE__ */ jsxRuntime.jsx(DefaultNextIcon, {})
|
|
775
|
+
}
|
|
776
|
+
),
|
|
777
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-lg font-medium capitalize ml-2", children: label })
|
|
778
|
+
] }),
|
|
779
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
780
|
+
showViewToggle && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
781
|
+
"select",
|
|
782
|
+
{
|
|
783
|
+
value: view,
|
|
784
|
+
onChange: toggleView,
|
|
785
|
+
className: "px-4 py-2 text-sm font-medium border rounded-lg bg-white hover:bg-gray-50 cursor-pointer",
|
|
786
|
+
children: [
|
|
787
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "week", children: weekLabel }),
|
|
788
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "day", children: dayLabel })
|
|
789
|
+
]
|
|
790
|
+
}
|
|
791
|
+
),
|
|
792
|
+
actions
|
|
793
|
+
] })
|
|
794
|
+
]
|
|
795
|
+
}
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
function useCalendarControls(options = {}) {
|
|
799
|
+
const {
|
|
800
|
+
initialDate = /* @__PURE__ */ new Date(),
|
|
801
|
+
initialView = "week",
|
|
802
|
+
locale = "es-MX"
|
|
803
|
+
} = options;
|
|
804
|
+
const [date, setDate] = react.useState(initialDate);
|
|
805
|
+
const [view, setView] = react.useState(initialView);
|
|
806
|
+
const week = react.useMemo(() => completeWeek(date), [date]);
|
|
807
|
+
const goToToday = react.useCallback(() => {
|
|
808
|
+
setDate(/* @__PURE__ */ new Date());
|
|
809
|
+
}, []);
|
|
810
|
+
const goToPrev = react.useCallback(() => {
|
|
811
|
+
setDate((d) => addDaysToDate(d, view === "week" ? -7 : -1));
|
|
812
|
+
}, [view]);
|
|
813
|
+
const goToNext = react.useCallback(() => {
|
|
814
|
+
setDate((d) => addDaysToDate(d, view === "week" ? 7 : 1));
|
|
815
|
+
}, [view]);
|
|
816
|
+
const toggleView = react.useCallback(() => {
|
|
817
|
+
setView((v) => v === "week" ? "day" : "week");
|
|
818
|
+
}, []);
|
|
819
|
+
const isToday2 = react.useMemo(() => {
|
|
820
|
+
const today = /* @__PURE__ */ new Date();
|
|
821
|
+
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
|
|
822
|
+
}, [date]);
|
|
823
|
+
const label = react.useMemo(() => {
|
|
824
|
+
if (view === "week") {
|
|
825
|
+
const monthName = week[0].toLocaleDateString(locale, { month: "long" });
|
|
826
|
+
const year = week[0].getFullYear();
|
|
827
|
+
const endMonth = week[6].getMonth();
|
|
828
|
+
if (week[0].getMonth() !== endMonth) {
|
|
829
|
+
const endMonthName = week[6].toLocaleDateString(locale, {
|
|
830
|
+
month: "short"
|
|
831
|
+
});
|
|
832
|
+
return `${week[0].getDate()} ${week[0].toLocaleDateString(locale, { month: "short" })} - ${week[6].getDate()} ${endMonthName} ${year}`;
|
|
833
|
+
}
|
|
834
|
+
return `${week[0].getDate()} - ${week[6].getDate()} ${monthName} ${year}`;
|
|
835
|
+
}
|
|
836
|
+
return date.toLocaleDateString(locale, {
|
|
837
|
+
weekday: "long",
|
|
838
|
+
day: "numeric",
|
|
839
|
+
month: "long",
|
|
840
|
+
year: "numeric"
|
|
841
|
+
});
|
|
842
|
+
}, [view, week, date, locale]);
|
|
843
|
+
return {
|
|
844
|
+
date,
|
|
845
|
+
view,
|
|
846
|
+
week,
|
|
847
|
+
label,
|
|
848
|
+
goToToday,
|
|
849
|
+
goToPrev,
|
|
850
|
+
goToNext,
|
|
851
|
+
toggleView,
|
|
852
|
+
setDate,
|
|
853
|
+
setView,
|
|
854
|
+
isToday: isToday2
|
|
855
|
+
};
|
|
856
|
+
}
|
|
630
857
|
|
|
631
858
|
exports.Calendar = Calendar;
|
|
859
|
+
exports.CalendarControls = CalendarControls;
|
|
632
860
|
exports.SimpleBigWeekView = Calendar;
|
|
633
861
|
exports.addDaysToDate = addDaysToDate;
|
|
634
862
|
exports.addMinutesToDate = addMinutesToDate;
|
|
@@ -642,6 +870,7 @@ exports.generateHours = generateHours;
|
|
|
642
870
|
exports.getDaysInMonth = getDaysInMonth;
|
|
643
871
|
exports.getMonday = getMonday;
|
|
644
872
|
exports.isToday = isToday;
|
|
873
|
+
exports.useCalendarControls = useCalendarControls;
|
|
645
874
|
exports.useCalendarEvents = useCalendarEvents;
|
|
646
875
|
exports.useClickOutside = useClickOutside;
|
|
647
876
|
exports.useEventOverlap = useEventOverlap;
|
package/dist/index.d.cts
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 };
|