@innosolutions/inno-calendar 1.0.33 → 1.0.34

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.
Files changed (45) hide show
  1. package/AGENT.md +244 -276
  2. package/README.md +64 -31
  3. package/dist/agenda-widget-B8blZmlr.cjs +2 -0
  4. package/dist/agenda-widget-B8blZmlr.cjs.map +1 -0
  5. package/dist/{agenda-widget-BXcLo1aO.js → agenda-widget-BwL7DItQ.js} +559 -440
  6. package/dist/agenda-widget-BwL7DItQ.js.map +1 -0
  7. package/dist/components/event/event-card.d.ts +63 -13
  8. package/dist/components/event/event-card.d.ts.map +1 -1
  9. package/dist/components/event/event-popover.d.ts +41 -9
  10. package/dist/components/event/event-popover.d.ts.map +1 -1
  11. package/dist/components/index.cjs +1 -1
  12. package/dist/components/index.mjs +2 -2
  13. package/dist/components/inno-calendar.d.ts +77 -10
  14. package/dist/components/inno-calendar.d.ts.map +1 -1
  15. package/dist/components/views/timeline-view.d.ts.map +1 -1
  16. package/dist/core/context/inno-calendar-provider.d.ts +4 -4
  17. package/dist/core/context/inno-calendar-provider.d.ts.map +1 -1
  18. package/dist/core/index.mjs +2 -2
  19. package/dist/core/types.d.ts +318 -58
  20. package/dist/core/types.d.ts.map +1 -1
  21. package/dist/index.cjs +1 -1
  22. package/dist/index.mjs +5 -5
  23. package/dist/presets/index.cjs +1 -1
  24. package/dist/presets/index.mjs +1 -1
  25. package/dist/{tailwind-calendar-D5PKQOjl.js → tailwind-calendar-BxW1RSkc.js} +2 -2
  26. package/dist/{tailwind-calendar-D5PKQOjl.js.map → tailwind-calendar-BxW1RSkc.js.map} +1 -1
  27. package/dist/{tailwind-calendar-BPGaoVW6.cjs → tailwind-calendar-CE6o8RLM.cjs} +2 -2
  28. package/dist/{tailwind-calendar-BPGaoVW6.cjs.map → tailwind-calendar-CE6o8RLM.cjs.map} +1 -1
  29. package/dist/{use-calendar-QYGDcRjd.js → use-calendar-DR2emWYC.js} +25 -6
  30. package/dist/use-calendar-DR2emWYC.js.map +1 -0
  31. package/dist/use-calendar-DR89bZu5.cjs.map +1 -1
  32. package/dist/{use-slot-selection-BKvxQ5zU.js → use-slot-selection-BJHlyfxL.js} +2 -2
  33. package/dist/{use-slot-selection-BKvxQ5zU.js.map → use-slot-selection-BJHlyfxL.js.map} +1 -1
  34. package/dist/week-view-DT8XnNbp.cjs +11 -0
  35. package/dist/week-view-DT8XnNbp.cjs.map +1 -0
  36. package/dist/{week-view-CJeYvlpO.js → week-view-jpcXSE6k.js} +642 -615
  37. package/dist/week-view-jpcXSE6k.js.map +1 -0
  38. package/package.json +1 -1
  39. package/dist/agenda-widget-BXcLo1aO.js.map +0 -1
  40. package/dist/agenda-widget-tZZgBGnS.cjs +0 -2
  41. package/dist/agenda-widget-tZZgBGnS.cjs.map +0 -1
  42. package/dist/use-calendar-QYGDcRjd.js.map +0 -1
  43. package/dist/week-view-CJeYvlpO.js.map +0 -1
  44. package/dist/week-view-DRGXG9E_.cjs +0 -11
  45. package/dist/week-view-DRGXG9E_.cjs.map +0 -1
package/AGENT.md CHANGED
@@ -1,15 +1,17 @@
1
1
  # @innosolutions/inno-calendar — AI Agent Reference
2
2
 
3
- > **Version**: 1.0.27 · **Runtime**: React 18+ / TypeScript · **Build**: Vite (library mode) + vite-plugin-dts
3
+ > **Version**: 1.0.33 · **Runtime**: React 18+ / TypeScript · **Build**: Vite (library mode) + vite-plugin-dts
4
4
  > **Published as**: `@innosolutions/inno-calendar` on npm (public)
5
5
 
6
- This document gives an AI coding agent all the context it needs to work in this repo correctly. Treat it as the single source of truth for types, props, exports, patterns, and integration.
6
+ This document gives an AI coding agent all the context it needs to work **in this
7
+ package** or **integrate it into a consuming application** correctly. Treat it as the
8
+ single source of truth for types, props, exports, patterns, and integration guidance.
7
9
 
8
10
  ---
9
11
 
10
12
  ## 1. What This Package Is
11
13
 
12
- A headless-first, fully customizable React calendar package extracted from **brick-erp-v2's agenda-v2 module**. It provides:
14
+ A headless-first, fully customizable React calendar package. It provides:
13
15
 
14
16
  - Multiple views: day, week, month, year, agenda, timeline, and resource views
15
17
  - Drag-to-select time slots for event creation
@@ -20,99 +22,17 @@ A headless-first, fully customizable React calendar package extracted from **bri
20
22
  - Working hours visualization with disabled-hour hatching
21
23
  - Widget components for dashboard embedding
22
24
  - Generic `CalendarEvent<TData>` — consumers bring their own data shape
25
+ - Built-in client-side filtering by schedule type, participants, and search query
26
+ - Preferences persistence (slot duration, working hours, badge variant, etc.)
23
27
 
24
- ### Consumer App: PromoSport ERP
25
-
26
- The primary consumer is `promosport-erp` (Next.js + tRPC). Its calendar-v2 module lives in:
27
- ```
28
- promosport-erp/src/app/[locale]/(protected)/calendar-v2/_components/
29
- ```
30
-
31
- Key files in the consumer:
32
- | File | Purpose |
33
- |------|---------|
34
- | `calendar-v2.main.tsx` | Wires InnoCalendar to tRPC queries, URL state (nuqs), filters |
35
- | `transformers.ts` | API data → `CalendarEvent<CalendarEventData>[]` transforms |
36
- | `calendar-filters.tsx` | Server-side filter panel (Sites/Events/Participants) via nuqs |
37
- | `event-popover.tsx` | Custom popover content rendered via `renderPopover` prop |
38
- | `create-drawer.tsx` | Header dropdown for creating event types |
39
- | `event-type-selector.tsx` | Dialog shown on slot select to choose event type |
40
- | `helpers.ts` | Date range computation per view for API fetch alignment |
41
- | `settings.tsx` | Calendar settings panel (slot duration, working hours, visible hours) |
42
- | `filter-colors.ts` | Color palette for event-linked filter contributions |
43
-
44
- ---
45
-
46
- ## 2. Package Structure
47
-
48
- ```
49
- src/
50
- index.ts # Main barrel export
51
- utils.ts # cn() helper re-export
52
-
53
- core/ # Headless core — NO UI dependencies
54
- index.ts # Core barrel export
55
- types.ts # ALL type definitions (~1140 lines)
56
- constants.ts # Defaults, view configs, color maps
57
- context/
58
- calendar-context.tsx # Base CalendarProvider
59
- inno-calendar-provider.tsx # Full provider with filtering/prefs
60
- drag-drop-context.tsx # Drag & drop state
61
- slot-selection-context.tsx # Drag-to-select state
62
- hooks/
63
- use-calendar.ts # Main calendar state hook
64
- use-calendar-time-config.ts # Time grid config hook
65
- use-preferences.ts # User preferences hook
66
- use-slot-selection.ts # Slot selection hook
67
- preferences/ # Advanced preferences with locking
68
- utils/
69
- date-utils.ts # Pure date functions (native Date API)
70
- event-utils.ts # Event filtering/sorting
71
- grid-utils.ts # View title, date range, navigation
72
- position-utils.ts # Event overlap/stacking
73
-
74
- components/ # UI Components
75
- inno-calendar.tsx # Main <InnoCalendar> component (~520 lines)
76
- calendar.tsx # Lower-level <Calendar> component
77
- integrated-calendar.tsx # Legacy alias
78
- event/
79
- event-card.tsx # EventCard, EventBlock, MultiDayEventBar (~1220 lines)
80
- event-popover.tsx # Built-in popover (overridable via renderPopover)
81
- header/
82
- calendar-header.tsx # Navigation, view switcher, settings
83
- components/
84
- date-navigator.tsx # Prev/Today/Next buttons
85
- today-button.tsx # Today button
86
- views/
87
- day-view.tsx # Single-day time grid
88
- week-view.tsx # 7-day time grid
89
- month-view.tsx # Month grid with overflow
90
- year-view.tsx # Year overview
91
- agenda-view.tsx # Flat event list grouped by day
92
- timeline-view.tsx # Resource timeline (rows per user)
93
- primitives/
94
- selectable-slot.tsx # Drag-to-select time slot
95
- multi-day-banner.tsx # All-day / multi-day event strip
96
- calendar-timeline.tsx # Timeline grid primitives
97
- filters/ # Built-in filter sidebar components
98
- settings/ # Preference controls (slot duration, etc.)
99
- widget/
100
- agenda-widget.tsx # Dashboard calendar widget
101
- agenda-dropdown.tsx # Dropdown variant of widget
102
- ui/ # Shadcn-style base components (button, tooltip, popover, etc.)
103
-
104
- presets/ # Pre-built configurations
105
- default/ # DefaultCalendar
106
- tailwind/ # TailwindCalendar
107
-
108
- styles/
109
- index.css # Base CSS entry
110
- calendar.css # Calendar-specific styles
111
- ```
28
+ The package is **fully data-agnostic**: it never fetches data, never knows about
29
+ any backend, and never imposes a domain model. Consumers provide an `events` array
30
+ and handle mutations themselves works with any data layer (React Query, SWR,
31
+ tRPC, plain fetch, etc.).
112
32
 
113
33
  ---
114
34
 
115
- ## 3. npm Scripts
35
+ ## 2. npm Scripts
116
36
 
117
37
  ```bash
118
38
  npm run dev # Vite dev server
@@ -124,30 +44,31 @@ npm run format # Biome format
124
44
 
125
45
  ---
126
46
 
127
- ## 4. Package Exports
47
+ ## 3. Package Exports
128
48
 
129
49
  ```jsonc
130
50
  {
131
- ".": "dist/index.{mjs,cjs,d.ts}", // Full API
132
- "./core": "dist/core/index.{mjs,cjs}", // Headless core only
51
+ ".": "dist/index.{mjs,cjs,d.ts}", // Full API
52
+ "./core": "dist/core/index.{mjs,cjs}", // Headless core only
133
53
  "./components": "dist/components/index.{mjs,cjs}", // UI components
134
- "./presets": "dist/presets/index.{mjs,cjs}", // Pre-built configs
135
- "./lib": "dist/lib/index.{mjs,cjs}", // cn() util
136
- "./utils": "dist/utils.{mjs,cjs}", // utils
137
- "./styles": "dist/styles/index.css", // CSS
138
- "./styles.css": "dist/styles/index.css" // CSS alias
54
+ "./presets": "dist/presets/index.{mjs,cjs}", // Pre-built configs
55
+ "./lib": "dist/lib/index.{mjs,cjs}", // cn() util
56
+ "./utils": "dist/utils.{mjs,cjs}", // utils
57
+ "./styles": "dist/styles/index.css", // CSS
58
+ "./styles.css": "dist/styles/index.css", // CSS alias
139
59
  }
140
60
  ```
141
61
 
142
- Consumer imports:
62
+ Typical consumer imports:
63
+
143
64
  ```tsx
144
- import { InnoCalendar, type CalendarEvent } from '@innosolutions/inno-calendar';
145
- import '@innosolutions/inno-calendar/styles';
65
+ import { InnoCalendar, type CalendarEvent } from "@innosolutions/inno-calendar";
66
+ import "@innosolutions/inno-calendar/styles";
146
67
  ```
147
68
 
148
69
  ---
149
70
 
150
- ## 5. Core Types (types.ts)
71
+ ## 4. Core Types (types.ts)
151
72
 
152
73
  ### CalendarEvent\<TData\>
153
74
 
@@ -163,39 +84,57 @@ interface CalendarEvent<TData = Record<string, unknown>> extends IBaseEvent {
163
84
 
164
85
  // Optional display fields
165
86
  description?: string;
166
- color?: TEventColor; // 'blue' | 'green' | 'red' | 'yellow' | 'purple' | 'orange' | 'pink' | 'teal' | 'gray' | 'indigo'
167
- hexColor?: string; // Hex override (takes precedence over color)
87
+ color?: TEventColor; // 'blue' | 'green' | 'red' | ... | 'indigo'
88
+ hexColor?: string; // Hex override (takes precedence over color)
168
89
  isAllDay?: boolean;
169
90
  isMultiDay?: boolean;
170
91
  isCanceled?: boolean;
171
92
  isRecurring?: boolean;
172
- resourceId?: string; // For resource/timeline views
173
- data?: TData; // Consumer's custom data bag
174
-
175
- // Deprecated (migrate to data.*)
176
- meetingTookPlace?: boolean;
177
- scheduleTypeId?: number;
178
- scheduleTypeName?: string;
179
- scheduleTypeCode?: string;
180
- cancelReason?: string | null;
181
- isAccepted?: boolean;
182
- companyId?: number;
183
- opportunityId?: number | null;
184
- propertyId?: number | null;
185
- projectId?: number | null;
186
- contactId?: number | null;
187
- participants?: ICalendarUser[];
188
- user?: ICalendarUser | null;
93
+ resourceId?: string; // For resource/timeline views
94
+
95
+ // Built-in filtering (optional — skip if you filter server-side)
96
+ scheduleTypeId?: number; // Enables built-in schedule type filtering
97
+ scheduleTypeName?: string; // Enables built-in search
98
+ participants?: ICalendarUser[]; // Enables built-in participant filtering
99
+
100
+ // @deprecated — still functional for backward compat, will be removed in next major.
101
+ // Migrate to the `data` bag. Built-in components read `data` first, then fall back here.
102
+ meetingTookPlace?: boolean; // → data.meetingTookPlace
103
+ isAccepted?: boolean; // → data.isAccepted
104
+ companyId?: number; // → data.companyId
105
+ cancelReason?: string | null; // → data.cancelReason
106
+ scheduleTypeCode?: string; // → data.scheduleTypeCode
107
+ opportunityId?: number; // → data.opportunityId
108
+ propertyId?: number; // → data.propertyId
109
+ projectId?: number; // → data.projectId
110
+ contactId?: number; // → data.contactId
111
+ user?: ICalendarUser; // → data.user
112
+
113
+ // Consumer's domain-specific data (RECOMMENDED)
114
+ data?: TData;
189
115
  }
190
116
  ```
191
117
 
118
+ **Data placement rule:** All domain-specific fields belong in `data?: TData`.
119
+ The deprecated top-level fields above are kept only for backward compatibility —
120
+ consumers should migrate values into the `data` bag. Built-in components
121
+ (EventCard, EventPopover, TimelineView) read from `event.data` first, then
122
+ fall back to the deprecated top-level fields.
123
+
192
124
  ### TCalendarView
193
125
 
194
126
  ```typescript
195
127
  type TCalendarView =
196
- | 'day' | 'week' | 'month' | 'year' | 'agenda'
197
- | 'resource-day' | 'resource-week'
198
- | 'timeline-day' | 'timeline-3day' | 'timeline-week';
128
+ | "day"
129
+ | "week"
130
+ | "month"
131
+ | "year"
132
+ | "agenda"
133
+ | "resource-day"
134
+ | "resource-week"
135
+ | "timeline-day"
136
+ | "timeline-3day"
137
+ | "timeline-week";
199
138
  ```
200
139
 
201
140
  ### ICalendarUser
@@ -204,7 +143,7 @@ type TCalendarView =
204
143
  interface ICalendarUser<TData = Record<string, unknown>> {
205
144
  id: string;
206
145
  name: string;
207
- email?: string; // Optional — employees endpoint does NOT return email
146
+ email?: string;
208
147
  avatar?: string;
209
148
  data?: TData;
210
149
  }
@@ -234,21 +173,21 @@ interface IScheduleType<TData = Record<string, unknown>> {
234
173
  interface ISelectionResult {
235
174
  startDate: Date;
236
175
  endDate: Date;
237
- resourceId?: string; // Set when selecting in resource/timeline views
176
+ resourceId?: string; // Set when selecting in resource/timeline views
238
177
  }
239
178
  ```
240
179
 
241
180
  ### TBadgeVariant
242
181
 
243
182
  ```typescript
244
- type TBadgeVariant = 'dot' | 'colored' | 'mixed';
183
+ type TBadgeVariant = "dot" | "colored" | "mixed";
245
184
  ```
246
185
 
247
186
  ---
248
187
 
249
- ## 6. InnoCalendar Props
188
+ ## 5. InnoCalendar Props
250
189
 
251
- The main `<InnoCalendar>` component (inno-calendar.tsx) accepts:
190
+ The main `<InnoCalendar>` component accepts:
252
191
 
253
192
  ```typescript
254
193
  interface InnoCalendarProps<TEventData> {
@@ -258,12 +197,12 @@ interface InnoCalendarProps<TEventData> {
258
197
  scheduleTypes?: IScheduleType[];
259
198
 
260
199
  // === INITIAL STATE ===
261
- initialView?: TCalendarView; // default: 'week'
262
- initialDate?: Date; // default: today
263
- initialSelectedUserId?: string | 'all';
200
+ initialView?: TCalendarView; // default: 'week'
201
+ initialDate?: Date; // default: today
202
+ initialSelectedUserId?: string | "all";
264
203
  initialScheduleTypeIds?: number[];
265
204
  initialParticipantIds?: string[];
266
- initialWorkingHoursView?: 'default' | 'enabled' | 'disabled';
205
+ initialWorkingHoursView?: "default" | "enabled" | "disabled";
267
206
  initialSearchQuery?: string;
268
207
 
269
208
  // === PREFERENCES ===
@@ -280,27 +219,30 @@ interface InnoCalendarProps<TEventData> {
280
219
 
281
220
  // === UI OPTIONS ===
282
221
  className?: string;
283
- showHeader?: boolean; // default: true
284
- minSelectionMinutes?: number; // default: 30
285
- showMoreEventsInPopover?: boolean; // default: false — "+N more" opens popover vs day view
222
+ showHeader?: boolean; // default: true
223
+ minSelectionMinutes?: number; // default: 30
224
+ showMoreEventsInPopover?: boolean; // default: false
286
225
 
287
226
  // === CUSTOM RENDERING ===
288
- renderPopover?: (props: { event: CalendarEvent<TEventData>; onClose: () => void }) => ReactNode;
227
+ renderPopover?: (props: {
228
+ event: CalendarEvent<TEventData>;
229
+ onClose: () => void;
230
+ }) => ReactNode;
289
231
 
290
232
  // === EXTENSIBILITY ===
291
233
  slots?: ICalendarSlots<TEventData>;
292
234
  classNames?: ICalendarClassNames;
293
235
 
294
236
  // === CUSTOM CONTENT SLOTS ===
295
- settingsContent?: ReactNode; // Settings panel content
296
- filterContent?: ReactNode; // Filter sidebar content
297
- headerActions?: ReactNode; // Replaces default add button in header
237
+ settingsContent?: ReactNode;
238
+ filterContent?: ReactNode;
239
+ headerActions?: ReactNode;
298
240
  }
299
241
  ```
300
242
 
301
243
  ---
302
244
 
303
- ## 7. Slots System (ICalendarSlots)
245
+ ## 6. Slots System (ICalendarSlots)
304
246
 
305
247
  Every visual region can be replaced:
306
248
 
@@ -344,7 +286,7 @@ interface ICalendarSlots<TEventData, TScheduleTypeData, TResourceData> {
344
286
 
345
287
  ---
346
288
 
347
- ## 8. ClassNames API (ICalendarClassNames)
289
+ ## 7. ClassNames API (ICalendarClassNames)
348
290
 
349
291
  Granular CSS class overrides merged via `cn()`:
350
292
 
@@ -400,177 +342,200 @@ interface ICalendarClassNames {
400
342
 
401
343
  ---
402
344
 
403
- ## 9. EventBlock Rich Data Feature
345
+ ## 8. EventCard Rich Data Display
404
346
 
405
- The `EventBlock` component (used in day/week time grids) extracts and displays rich data from `event.data`:
347
+ The `EventCard` component (used in day/week time grids) extracts and displays
348
+ rich data from `event.data`, then falls back to deprecated top-level fields:
406
349
 
407
350
  ```typescript
408
- // Fields extracted from event.data
351
+ // Fields extracted from event.data (safe runtime access, then deprecated fallback)
409
352
  const eventData = event.data as Record<string, unknown> | undefined;
410
- const scheduleTypeName = event.scheduleTypeName || (eventData?.typeName as string);
353
+ const meetingTookPlace =
354
+ (eventData?.meetingTookPlace as boolean) ?? event.meetingTookPlace ?? false;
355
+ const scheduleTypeName =
356
+ event.scheduleTypeName ||
357
+ (eventData?.scheduleTypeName as string) ||
358
+ (eventData?.typeName as string);
411
359
  const productName = eventData?.productName as string;
412
360
  const nrParticipant = eventData?.nrParticipant as number;
413
361
  const siteName = eventData?.siteName as string;
414
362
  const siteCity = eventData?.siteCity as string;
415
- const locationStr = [siteName, siteCity].filter(Boolean).join(', ');
363
+ const locationStr = [siteName, siteCity].filter(Boolean).join(", ");
416
364
  ```
417
365
 
418
366
  Display rules based on event duration:
419
- | Duration | Shows |
420
- |----------|-------|
421
- | < 25 min | Title only (centered) |
422
- | 25–44 min | Title + time (with ClockIcon) |
423
- | 45 min | Full details: title+type, product, participants, time, location |
367
+
368
+ | Duration | Shows |
369
+ | --------- | ----------------------------------------------------------------- |
370
+ | < 25 min | Title only (centered) |
371
+ | 25–44 min | Title + time (with ClockIcon) |
372
+ | ≥ 45 min | Full details: title + type, product, participants, time, location |
424
373
 
425
374
  The title line format: `{title}` + optional ` - {scheduleTypeName}` in lighter weight.
426
375
 
427
376
  ---
428
377
 
429
- ## 10. PromoSport Integration Patterns
378
+ ## 9. Integration Guide
430
379
 
431
- ### Data Flow
380
+ This section documents the common patterns consumers use when integrating the
381
+ package. Adapt to whatever data layer, routing, or UI framework the consuming
382
+ application uses.
432
383
 
433
- ```
434
- API (calendar/events) → tRPC router → React Query
435
-
436
- transformCalendarEvents() in transformers.ts
437
-
438
- CalendarEvent<CalendarEventData>[] → <InnoCalendar events={...} />
439
- ```
384
+ ### 9.1 Transform API Data → CalendarEvent
440
385
 
441
- ### CalendarEventData (PromoSport's TData)
386
+ Every consumer needs a **transformer layer** that maps its API response shapes to
387
+ the package's generic `CalendarEvent<TData>` type. Place these in a `transformers.ts`
388
+ (or similar) file, co-located with the calendar feature.
442
389
 
443
390
  ```typescript
444
- interface CalendarEventData {
391
+ // Example: transformers.ts in the consuming app
392
+ import type {
393
+ CalendarEvent,
394
+ ICalendarUser,
395
+ IScheduleType,
396
+ } from "@innosolutions/inno-calendar";
397
+
398
+ /** Define the shape of your domain-specific event data. */
399
+ interface MyEventData {
445
400
  [key: string]: unknown;
446
- typeId: number; // 2=Training, 3=Vacation, 4=Group, 5=Intern, 7=Private, 8=Absence
447
- typeName: string;
448
- groupId?: number;
449
- groupTitle?: string;
450
- productName?: string;
451
- nrParticipant?: number;
452
- monitors?: string[];
453
- siteName?: string;
454
- sitePostalCode?: string;
455
- siteCity?: string;
456
- siteStreet?: string;
457
- holdingConfirmed?: boolean;
401
+ agendaTypeId: number;
402
+ agendaTypeName: string;
403
+ companyId: number;
404
+ meetingTookPlace: boolean;
405
+ isAccepted: boolean;
458
406
  cancelReason?: string;
407
+ participantNames: string[];
459
408
  }
460
- ```
461
-
462
- ### Event Type Color Map
463
409
 
464
- ```typescript
465
- const eventTypeColorMap: Record<number, TEventColor> = {
466
- 2: 'blue', // Training Session
467
- 3: 'purple', // Vacation
468
- 4: 'green', // Event Group Session
469
- 5: 'orange', // Intern Meet
470
- 7: 'red', // Private Event
471
- 8: 'pink', // Absence
472
- };
410
+ /**
411
+ * Transform raw API events into CalendarEvent[].
412
+ * Core fields at top level, domain data in the `data` bag.
413
+ */
414
+ export function transformEvents(
415
+ apiEvents: ApiEvent[],
416
+ ): CalendarEvent<MyEventData>[] {
417
+ return apiEvents.map((event) => ({
418
+ id: event.id.toString(),
419
+ title: event.title,
420
+ startDate: new Date(event.startDate),
421
+ endDate: new Date(event.endDate),
422
+ description: event.description ?? undefined,
423
+ color: eventTypeColorMap[event.type.id] ?? "blue",
424
+ hexColor: eventTypeHexMap[event.type.id] ?? "#069AD7",
425
+ isCanceled: event.isCanceled,
426
+ isAllDay: false,
427
+ isMultiDay: isDifferentDay(
428
+ new Date(event.startDate),
429
+ new Date(event.endDate),
430
+ ),
431
+
432
+ // Built-in filtering (optional — omit if you filter server-side)
433
+ scheduleTypeId: event.type.id,
434
+ scheduleTypeName: event.type.name,
435
+
436
+ // Domain-specific data (RECOMMENDED location)
437
+ data: {
438
+ agendaTypeId: event.type.id,
439
+ agendaTypeName: event.type.name,
440
+ companyId: event.companyId,
441
+ meetingTookPlace: event.meetingTookPlace,
442
+ isAccepted: event.isAccepted,
443
+ cancelReason: event.cancelReason,
444
+ participantNames: event.participants?.map((p) => p.name) ?? [],
445
+ },
446
+ }));
447
+ }
473
448
  ```
474
449
 
475
- ### Monitor Resolution
476
-
477
- The API returns monitor names as strings (`"John Doe"`). The calendar needs user IDs for timeline views. The flow:
450
+ ### 9.2 Filtering Strategies
478
451
 
479
- 1. `getEventParticipants` tRPC endpoint calls `data/employees` returns `Options[]` with `{ id, name }` (no email)
480
- 2. `buildMonitorLookup(participants)` builds `Map<string, string>` (lowercased name → id)
481
- 3. `transformCalendarEvents()` resolves each monitor name to an ID via the lookup
482
- 4. Fallback: slugified name if no match (`"john-doe"`)
452
+ The package supports **two** filtering approaches pick whichever fits:
483
453
 
484
- ### Filtering Architecture
454
+ | Strategy | How it works | When to use |
455
+ | -------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------- |
456
+ | **Client-side** (built-in) | Provide `scheduleTypeId`, `participants` on events + set initial filter IDs | Small datasets, no server-side filter endpoint |
457
+ | **Server-side** | Filter events at the API level; pass already-filtered `events[]` | Large datasets, URL-driven filters, pagination |
485
458
 
486
- PromoSport uses **server-side filtering via URL params** (not InnoCalendar's built-in client-side filtering):
459
+ For server-side filtering, build a filter panel as a regular form component and
460
+ pass it via `filterContent`:
487
461
 
488
- ```
489
- nuqs URL state (?sites=1,2&events=5&participants=42)
490
-
491
- calendar-v2.main.tsx reads filterParams
492
-
493
- tRPC query key includes filter params → API returns filtered data
494
-
495
- InnoCalendar renders the already-filtered events
462
+ ```tsx
463
+ <InnoCalendar
464
+ events={filteredEventsFromApi}
465
+ filterContent={<MyFilterPanel />}
466
+ />
496
467
  ```
497
468
 
498
- The `<CalendarFilters>` component is passed via `filterContent` prop and writes to URL state independently.
469
+ The filter panel writes to URL state (or any state manager); the data query
470
+ includes those filter values; the calendar just renders whatever `events` it receives.
499
471
 
500
- ### Event Popover
472
+ ### 9.3 Custom Event Popover
501
473
 
502
- Custom popover content is passed via `renderPopover`:
474
+ The built-in popover works out of the box, but most applications customize it to
475
+ show domain-specific details (linked entities, action buttons, RSVP status, etc.).
476
+ Override via `renderPopover`:
503
477
 
504
478
  ```tsx
505
479
  <InnoCalendar
506
480
  renderPopover={({ event, onClose }) => (
507
- <EventPopoverContent event={event} onClose={onClose} />
481
+ <MyEventPopover event={event} onClose={onClose} />
508
482
  )}
509
483
  />
510
484
  ```
511
485
 
512
- The popover:
513
- 1. Fetches full event details via `getCalendarEventById` (list endpoint has minimal data)
514
- 2. Shows type-specific details (vacation: employee + dates; group: participants, monitors, site, time)
515
- 3. **Excludes monitors from the participants list** (case-insensitive name match filtering)
516
- 4. Shows action buttons based on type + state (Cancel, Edit, Confirm Held)
486
+ A custom popover typically:
517
487
 
518
- ### Header Actions
488
+ 1. Fetches full event details on open (list endpoints often return minimal data)
489
+ 2. Shows domain-specific fields from `event.data` (cancel reason, linked contacts, etc.)
490
+ 3. Provides action buttons (edit, cancel, delete, confirm, etc.)
519
491
 
520
- Custom create dropdown replaces the default add button:
492
+ ### 9.4 Custom Header Actions
493
+
494
+ Replace the default "Add Event" button with your own header actions (dropdown
495
+ menus, multiple create buttons per event type, etc.):
521
496
 
522
497
  ```tsx
523
- <InnoCalendar headerActions={<CreateDrawer />} />
498
+ <InnoCalendar headerActions={<MyCreateDropdown />} />
524
499
  ```
525
500
 
526
- ### Slot Selection → Event Type Dialog
501
+ ### 9.5 Slot Selection → Event Creation
527
502
 
528
- When a user drags a time slot, `onSlotSelect` opens a dialog with event type buttons:
503
+ When a user drags across time slots, `onSlotSelect` fires with the selected
504
+ date range. The consumer decides what happens next — open a form directly, show a
505
+ dialog to pick the event type, or create an event immediately:
529
506
 
530
507
  ```tsx
531
508
  onSlotSelect={(selection) => {
532
- dialog.initialize({
533
- title: t('buttons.create'),
534
- children: <EventTypeSelector startDate={selection.startDate} endDate={selection.endDate} />,
535
- });
509
+ // Option A: open a create form directly with pre-filled dates
510
+ openCreateForm({ startDate: selection.startDate, endDate: selection.endDate });
511
+
512
+ // Option B: show an event type picker first, then route to the right form
513
+ openEventTypePicker(selection);
536
514
  }}
537
515
  ```
538
516
 
539
- ---
540
-
541
- ## 11. API Endpoints Used by PromoSport
517
+ Whether you need a type-picker step depends on whether the app has multiple
518
+ event categories. Single-type apps can skip straight to the form.
542
519
 
543
- | tRPC Procedure | Backend Endpoint | Returns | Notes |
544
- |----------------|------------------|---------|-------|
545
- | `calendarRouter.getCalendarEvents` | `calendar/events` | `CalendarEvents[]` | Main event list, filtered by date/sites/participants/events |
546
- | `calendarRouter.getCalendarEventById` | `calendar/events/{id}` | `CalendarEventById` | Full details for popover |
547
- | `calendarRouter.getEventTypes` | `calendar/events/types` | `Options[]` | Event type categories |
548
- | `calendarRouter.getEventParticipants` | `data/employees` | `Options[]` | Employee list (`{ id, name }`, no email) |
549
- | `dataRouter.getSites` | `data/sites` | Sites with address | For filter sidebar |
550
- | `dataRouter.getEvents` | `data/events` | Events with monitors/sites | For event filter with contribution tracking |
520
+ ### 9.6 Settings Panel
551
521
 
552
- ### EventParticipants Interface
522
+ The package ships built-in settings controls (slot duration, visible hours,
523
+ working hours, badge variant). Pass custom content via `settingsContent` to
524
+ extend or replace them:
553
525
 
554
- ```typescript
555
- // Matches the employees endpoint response shape
556
- interface EventParticipants {
557
- id: number;
558
- name: string; // Full name ("Aaron CIMANGA")
559
- description?: string; // Always null from employees endpoint
560
- isArchived?: boolean;
561
- }
526
+ ```tsx
527
+ <InnoCalendar settingsContent={<MySettingsPanel />} />
562
528
  ```
563
529
 
564
- **Important**: No `firstName`, `lastName`, or `email` fields. Always use `name` directly.
565
-
566
- ---
530
+ Most apps will customize this to include project-specific preferences or
531
+ integrate with a persistence layer that saves settings per user.
567
532
 
568
- ## 12. Working Hours & Disabled Slots
533
+ ### 9.7 Working Hours & Disabled Slots
569
534
 
570
- Non-working-hour time slots get a CSS hatching pattern. The consumer provides this via CSS:
535
+ Non-working-hour time slots receive the CSS class `inno-calendar-disabled-hour`.
536
+ The consumer provides the hatching/dimming style:
571
537
 
572
538
  ```css
573
- /* In promosport-erp/calendar-v2.css */
574
539
  .inno-calendar-disabled-hour,
575
540
  .bg-calendar-disabled-hour {
576
541
  background-image: repeating-linear-gradient(
@@ -581,11 +546,12 @@ Non-working-hour time slots get a CSS hatching pattern. The consumer provides th
581
546
  }
582
547
  ```
583
548
 
584
- Working hours are configurable per day via the settings panel (CalendarSettingsContent).
549
+ Working hours are configurable per day via the built-in settings controls or
550
+ programmatically via `useInnoCalendar().setWorkingHours()`.
585
551
 
586
552
  ---
587
553
 
588
- ## 13. Context Hooks
554
+ ## 10. Context Hooks
589
555
 
590
556
  ### useInnoCalendar\<TEventData\>()
591
557
 
@@ -625,24 +591,26 @@ The main context hook. Returns:
625
591
 
626
592
  ---
627
593
 
628
- ## 14. File Naming Conventions
594
+ ## 11. File Naming Conventions
595
+
596
+ All files use **kebab-case** naming:
629
597
 
630
- All files use **kebab-case** naming (NOT PascalCase — the copilot-instructions.md is aspirational, the actual codebase uses kebab-case):
598
+ | Type | Convention | Example |
599
+ | ---------- | ------------------------ | ------------------------------------------ |
600
+ | Components | `kebab-case.tsx` | `event-card.tsx`, `week-view.tsx` |
601
+ | Hooks | `use-kebab-case.ts` | `use-calendar.ts`, `use-slot-selection.ts` |
602
+ | Context | `kebab-case-context.tsx` | `calendar-context.tsx` |
603
+ | Types | `types.ts` | `types.ts` |
604
+ | Utilities | `kebab-case.ts` | `date-utils.ts`, `position-utils.ts` |
605
+ | Constants | `constants.ts` | `constants.ts` |
606
+ | Index | `index.ts` | `index.ts` |
607
+ | Styles | `kebab-case.css` | `calendar.css` |
631
608
 
632
- | Type | Convention | Example |
633
- |------|------------|---------|
634
- | Components | `kebab-case.tsx` | `event-card.tsx`, `week-view.tsx` |
635
- | Hooks | `use-kebab-case.ts` | `use-calendar.ts`, `use-slot-selection.ts` |
636
- | Context | `kebab-case-context.tsx` | `calendar-context.tsx` |
637
- | Types | `types.ts` | `types.ts` |
638
- | Utilities | `kebab-case.ts` | `date-utils.ts`, `position-utils.ts` |
639
- | Constants | `constants.ts` | `constants.ts` |
640
- | Index | `index.ts` | `index.ts` |
641
- | Styles | `kebab-case.css` | `calendar.css` |
609
+ Folders also use kebab-case: `core/`, `components/`, `presets/`.
642
610
 
643
611
  ---
644
612
 
645
- ## 15. Styling
613
+ ## 12. Styling
646
614
 
647
615
  - **TailwindCSS 4+** with `cn()` helper (clsx + tailwind-merge)
648
616
  - **class-variance-authority (CVA)** for component variants
@@ -652,7 +620,7 @@ All files use **kebab-case** naming (NOT PascalCase — the copilot-instructions
652
620
 
653
621
  ---
654
622
 
655
- ## 16. Biome Configuration
623
+ ## 13. Biome Configuration
656
624
 
657
625
  - Tabs for indentation
658
626
  - Single quotes
@@ -664,15 +632,15 @@ Run before commits: `npm run format && npm run lint && npm run typecheck`
664
632
 
665
633
  ---
666
634
 
667
- ## 17. Critical Rules
635
+ ## 14. Critical Rules
668
636
 
669
637
  1. **Never add external dependencies to `core/`** — only React and native APIs
670
- 2. **`event-card.tsx` must match brick-erp-v2/agenda-v2 styling** it's the source of truth
671
- 3. **Rich data in EventBlock reads from `event.data.*`** not top-level deprecated fields
672
- 4. **Employee data has NO email** — the `data/employees` endpoint returns `{ id, name }` only
673
- 5. **Monitors and participants are separate** — in popovers, filter monitors out of participants by case-insensitive name match
674
- 6. **Consumers handle data fetching** this package never calls APIs
675
- 7. **URL-based filtering is the consumer's responsibility** InnoCalendar just renders what it receives
676
- 8. **All public APIs need JSDoc** with `@example` blocks
677
- 9. **Use section separators** (`// ====... TYPES ====...`) to organize code
678
- 10. **Run `npm run typecheck` before committing** the consumer project also typechecks against this package
638
+ 2. **Rich data in EventCard reads from `event.data` first**, then falls back to deprecated top-level fields
639
+ 3. **CalendarEvent is domain-agnostic** domain fields go in `data`, deprecated top-level fields stay until next major
640
+ 4. **Consumers handle data fetching** — this package never calls APIs
641
+ 5. **Filtering is the consumer's choice** — built-in client-side filtering is available via `scheduleTypeId` / `participants`, or consumers filter server-side and pass pre-filtered events
642
+ 6. **All public APIs need JSDoc** with `@example` blocks
643
+ 7. **Use section separators** (`// ====... TYPES ====...`) to organize code
644
+ 8. **Run `npm run typecheck` before committing**
645
+ 9. **Files and folders use kebab-case** never PascalCase
646
+ 10. **Update AGENT.md, README.md, and .github/copilot-instructions.md** when changing public APIs