@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.cjs
CHANGED
|
@@ -217,21 +217,40 @@ var cn = (...classes) => classes.filter(Boolean).join(" ");
|
|
|
217
217
|
var DefaultTrashIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.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" }) });
|
|
218
218
|
var DefaultEditIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.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" }) });
|
|
219
219
|
var DefaultCloseIcon = () => /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", className: "w-4 h-4", fill: "currentColor", children: /* @__PURE__ */ jsxRuntime.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" }) });
|
|
220
|
-
var DayHeader = ({
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
220
|
+
var DayHeader = ({
|
|
221
|
+
date,
|
|
222
|
+
locale,
|
|
223
|
+
index,
|
|
224
|
+
resource,
|
|
225
|
+
renderColumnHeader
|
|
226
|
+
}) => {
|
|
227
|
+
const isToday2 = isToday(date);
|
|
228
|
+
if (renderColumnHeader) {
|
|
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
|
+
] });
|
|
236
|
+
}
|
|
237
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "grid place-items-center", children: [
|
|
238
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
|
|
239
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
240
|
+
"span",
|
|
241
|
+
{
|
|
242
|
+
className: cn(
|
|
243
|
+
isToday2 && "bg-blue-500 rounded-full p-1 text-white"
|
|
244
|
+
),
|
|
245
|
+
children: date.getDate()
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
] });
|
|
249
|
+
};
|
|
232
250
|
function Calendar({
|
|
233
251
|
date = /* @__PURE__ */ new Date(),
|
|
234
252
|
events = [],
|
|
253
|
+
resources,
|
|
235
254
|
onEventClick,
|
|
236
255
|
onNewEvent,
|
|
237
256
|
onEventMove,
|
|
@@ -239,10 +258,12 @@ function Calendar({
|
|
|
239
258
|
onRemoveBlock,
|
|
240
259
|
config = {}
|
|
241
260
|
}) {
|
|
242
|
-
const { locale = "es-MX", icons = {} } = config;
|
|
261
|
+
const { locale = "es-MX", icons = {}, renderColumnHeader } = config;
|
|
243
262
|
const week = completeWeek(date);
|
|
244
263
|
const [activeId, setActiveId] = react.useState(null);
|
|
245
264
|
const { canMove } = useCalendarEvents(events);
|
|
265
|
+
const isResourceMode = !!resources && resources.length > 0;
|
|
266
|
+
const columnCount = isResourceMode ? resources.length : 7;
|
|
246
267
|
const sensors = core.useSensors(
|
|
247
268
|
core.useSensor(core.PointerSensor, {
|
|
248
269
|
activationConstraint: { distance: 8 }
|
|
@@ -257,10 +278,10 @@ function Calendar({
|
|
|
257
278
|
setActiveId(null);
|
|
258
279
|
if (!over) return;
|
|
259
280
|
const eventId = active.id.toString().replace("event-", "");
|
|
260
|
-
const [,
|
|
261
|
-
const
|
|
281
|
+
const [, colIndexStr, hourStr] = over.id.toString().split("-");
|
|
282
|
+
const colIndex = parseInt(colIndexStr);
|
|
262
283
|
const hour = parseInt(hourStr);
|
|
263
|
-
const targetDay = week[
|
|
284
|
+
const targetDay = isResourceMode ? date : week[colIndex];
|
|
264
285
|
const newStart = new Date(targetDay);
|
|
265
286
|
newStart.setHours(hour, 0, 0, 0);
|
|
266
287
|
const movedEvent = events.find((e) => e.id === eventId);
|
|
@@ -279,6 +300,24 @@ function Calendar({
|
|
|
279
300
|
setActiveId(null);
|
|
280
301
|
};
|
|
281
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
|
+
};
|
|
282
321
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
283
322
|
core.DndContext,
|
|
284
323
|
{
|
|
@@ -288,32 +327,66 @@ function Calendar({
|
|
|
288
327
|
onDragEnd: handleDragEnd,
|
|
289
328
|
onDragCancel: handleDragCancel,
|
|
290
329
|
children: [
|
|
291
|
-
/* @__PURE__ */ jsxRuntime.jsxs("article", { className: "w-full bg-white shadow rounded-xl", children: [
|
|
292
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
+
)
|
|
317
390
|
] }),
|
|
318
391
|
/* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { children: activeEvent ? /* @__PURE__ */ jsxRuntime.jsx(EventOverlay, { event: activeEvent }) : null })
|
|
319
392
|
]
|
|
@@ -411,6 +484,42 @@ var EmptyButton = ({
|
|
|
411
484
|
}
|
|
412
485
|
);
|
|
413
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
|
+
};
|
|
414
523
|
var Column = ({
|
|
415
524
|
onEventClick,
|
|
416
525
|
events = [],
|
|
@@ -420,9 +529,11 @@ var Column = ({
|
|
|
420
529
|
onRemoveBlock,
|
|
421
530
|
dayIndex,
|
|
422
531
|
locale,
|
|
423
|
-
icons
|
|
532
|
+
icons,
|
|
533
|
+
resourceId
|
|
424
534
|
}) => {
|
|
425
535
|
const columnRef = react.useRef(null);
|
|
536
|
+
const eventsWithPositions = calculateOverlapPositions(events);
|
|
426
537
|
react.useEffect(() => {
|
|
427
538
|
if (!columnRef.current) return;
|
|
428
539
|
const today = /* @__PURE__ */ new Date();
|
|
@@ -436,21 +547,24 @@ var Column = ({
|
|
|
436
547
|
}, 100);
|
|
437
548
|
}
|
|
438
549
|
}, [dayOfWeek]);
|
|
439
|
-
const
|
|
440
|
-
const
|
|
441
|
-
(event) => new Date(event.start).getHours() === hours
|
|
550
|
+
const findEventsAtHour = (hours) => {
|
|
551
|
+
const eventsStartingHere = eventsWithPositions.filter(
|
|
552
|
+
({ event }) => new Date(event.start).getHours() === hours
|
|
442
553
|
);
|
|
443
|
-
if (
|
|
444
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
554
|
+
if (eventsStartingHere.length > 0) {
|
|
555
|
+
return eventsStartingHere.map(({ event, column, totalColumns }) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
445
556
|
DraggableEvent,
|
|
446
557
|
{
|
|
447
|
-
onClick: () => onEventClick?.(
|
|
448
|
-
event
|
|
558
|
+
onClick: () => onEventClick?.(event),
|
|
559
|
+
event,
|
|
449
560
|
onRemoveBlock,
|
|
450
561
|
locale,
|
|
451
|
-
icons
|
|
452
|
-
|
|
453
|
-
|
|
562
|
+
icons,
|
|
563
|
+
overlapColumn: column,
|
|
564
|
+
overlapTotal: totalColumns
|
|
565
|
+
},
|
|
566
|
+
event.id
|
|
567
|
+
));
|
|
454
568
|
}
|
|
455
569
|
const eventSpansHere = events.find((event) => {
|
|
456
570
|
const eventStart = new Date(event.start);
|
|
@@ -476,7 +590,7 @@ var Column = ({
|
|
|
476
590
|
date: dayOfWeek,
|
|
477
591
|
className: "relative",
|
|
478
592
|
dayIndex,
|
|
479
|
-
children:
|
|
593
|
+
children: findEventsAtHour(hours)
|
|
480
594
|
},
|
|
481
595
|
hours
|
|
482
596
|
)) });
|
|
@@ -487,16 +601,22 @@ var DraggableEvent = ({
|
|
|
487
601
|
onClick,
|
|
488
602
|
onRemoveBlock,
|
|
489
603
|
locale,
|
|
490
|
-
icons
|
|
604
|
+
icons,
|
|
605
|
+
overlapColumn = 0,
|
|
606
|
+
overlapTotal = 1
|
|
491
607
|
}) => {
|
|
492
608
|
const [showOptions, setShowOptions] = react.useState(false);
|
|
493
609
|
const { attributes, listeners, setNodeRef, transform, isDragging } = core.useDraggable({
|
|
494
610
|
id: `event-${event.id}`,
|
|
495
611
|
disabled: event.type === "BLOCK"
|
|
496
612
|
});
|
|
613
|
+
const widthPercent = event.type === "BLOCK" ? 100 : 90 / overlapTotal;
|
|
614
|
+
const leftPercent = event.type === "BLOCK" ? 0 : overlapColumn * (90 / overlapTotal);
|
|
497
615
|
const style = {
|
|
498
616
|
height: event.duration / 60 * 64,
|
|
499
|
-
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}%`
|
|
500
620
|
};
|
|
501
621
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
502
622
|
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -509,7 +629,7 @@ var DraggableEvent = ({
|
|
|
509
629
|
...attributes,
|
|
510
630
|
className: cn(
|
|
511
631
|
"border grid gap-y-1 overflow-hidden place-content-start",
|
|
512
|
-
"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",
|
|
513
633
|
event.type === "BLOCK" && "bg-gray-300 h-full w-full text-center cursor-not-allowed relative p-0",
|
|
514
634
|
event.type !== "BLOCK" && "cursor-grab",
|
|
515
635
|
isDragging && event.type !== "BLOCK" && "cursor-grabbing opacity-50"
|
|
@@ -607,8 +727,136 @@ var Options = ({
|
|
|
607
727
|
}
|
|
608
728
|
);
|
|
609
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
|
+
}
|
|
610
857
|
|
|
611
858
|
exports.Calendar = Calendar;
|
|
859
|
+
exports.CalendarControls = CalendarControls;
|
|
612
860
|
exports.SimpleBigWeekView = Calendar;
|
|
613
861
|
exports.addDaysToDate = addDaysToDate;
|
|
614
862
|
exports.addMinutesToDate = addMinutesToDate;
|
|
@@ -622,6 +870,7 @@ exports.generateHours = generateHours;
|
|
|
622
870
|
exports.getDaysInMonth = getDaysInMonth;
|
|
623
871
|
exports.getMonday = getMonday;
|
|
624
872
|
exports.isToday = isToday;
|
|
873
|
+
exports.useCalendarControls = useCalendarControls;
|
|
625
874
|
exports.useCalendarEvents = useCalendarEvents;
|
|
626
875
|
exports.useClickOutside = useClickOutside;
|
|
627
876
|
exports.useEventOverlap = useEventOverlap;
|
package/dist/index.d.cts
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 };
|