@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/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 = ({ date, locale }) => /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "grid place-items-center", children: [
221
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: date.toLocaleDateString(locale, { weekday: "short" }) }),
222
- /* @__PURE__ */ jsxRuntime.jsx(
223
- "span",
224
- {
225
- className: cn(
226
- isToday(date) && "bg-blue-500 rounded-full p-1 text-white"
227
- ),
228
- children: date.getDate()
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 [, dayIndexStr, hourStr] = over.id.toString().split("-");
261
- const dayIndex = parseInt(dayIndexStr);
281
+ const [, colIndexStr, hourStr] = over.id.toString().split("-");
282
+ const colIndex = parseInt(colIndexStr);
262
283
  const hour = parseInt(hourStr);
263
- const targetDay = week[dayIndex];
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("section", { className: "grid grid-cols-8 place-items-center py-4", children: [
293
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: Intl.DateTimeFormat().resolvedOptions().timeZone }) }),
294
- week.map((day) => /* @__PURE__ */ jsxRuntime.jsx(DayHeader, { date: day, locale }, day.toISOString()))
295
- ] }),
296
- /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "grid grid-cols-8 max-h-[80vh] overflow-y-auto", children: [
297
- /* @__PURE__ */ jsxRuntime.jsx(TimeColumn, {}),
298
- week.map((dayOfWeek, dayIndex) => /* @__PURE__ */ jsxRuntime.jsx(
299
- Column,
300
- {
301
- dayIndex,
302
- dayOfWeek,
303
- events: events.filter((event) => {
304
- const eventDate = new Date(event.start);
305
- return eventDate.getDate() === dayOfWeek.getDate() && eventDate.getMonth() === dayOfWeek.getMonth();
306
- }),
307
- onNewEvent,
308
- onAddBlock,
309
- onRemoveBlock,
310
- onEventClick,
311
- locale,
312
- icons
313
- },
314
- dayOfWeek.toISOString()
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 findEvent = (hours) => {
440
- const eventStartsHere = events.find(
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 (eventStartsHere) {
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?.(eventStartsHere),
448
- event: eventStartsHere,
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: findEvent(hours)
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 left-0 bg-blue-500 text-white rounded-md z-10 w-[90%]",
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 };