@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.
- package/AGENT.md +244 -276
- package/README.md +64 -31
- package/dist/agenda-widget-B8blZmlr.cjs +2 -0
- package/dist/agenda-widget-B8blZmlr.cjs.map +1 -0
- package/dist/{agenda-widget-BXcLo1aO.js → agenda-widget-BwL7DItQ.js} +559 -440
- package/dist/agenda-widget-BwL7DItQ.js.map +1 -0
- package/dist/components/event/event-card.d.ts +63 -13
- package/dist/components/event/event-card.d.ts.map +1 -1
- package/dist/components/event/event-popover.d.ts +41 -9
- package/dist/components/event/event-popover.d.ts.map +1 -1
- package/dist/components/index.cjs +1 -1
- package/dist/components/index.mjs +2 -2
- package/dist/components/inno-calendar.d.ts +77 -10
- package/dist/components/inno-calendar.d.ts.map +1 -1
- package/dist/components/views/timeline-view.d.ts.map +1 -1
- package/dist/core/context/inno-calendar-provider.d.ts +4 -4
- package/dist/core/context/inno-calendar-provider.d.ts.map +1 -1
- package/dist/core/index.mjs +2 -2
- package/dist/core/types.d.ts +318 -58
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +5 -5
- package/dist/presets/index.cjs +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/{tailwind-calendar-D5PKQOjl.js → tailwind-calendar-BxW1RSkc.js} +2 -2
- package/dist/{tailwind-calendar-D5PKQOjl.js.map → tailwind-calendar-BxW1RSkc.js.map} +1 -1
- package/dist/{tailwind-calendar-BPGaoVW6.cjs → tailwind-calendar-CE6o8RLM.cjs} +2 -2
- package/dist/{tailwind-calendar-BPGaoVW6.cjs.map → tailwind-calendar-CE6o8RLM.cjs.map} +1 -1
- package/dist/{use-calendar-QYGDcRjd.js → use-calendar-DR2emWYC.js} +25 -6
- package/dist/use-calendar-DR2emWYC.js.map +1 -0
- package/dist/use-calendar-DR89bZu5.cjs.map +1 -1
- package/dist/{use-slot-selection-BKvxQ5zU.js → use-slot-selection-BJHlyfxL.js} +2 -2
- package/dist/{use-slot-selection-BKvxQ5zU.js.map → use-slot-selection-BJHlyfxL.js.map} +1 -1
- package/dist/week-view-DT8XnNbp.cjs +11 -0
- package/dist/week-view-DT8XnNbp.cjs.map +1 -0
- package/dist/{week-view-CJeYvlpO.js → week-view-jpcXSE6k.js} +642 -615
- package/dist/week-view-jpcXSE6k.js.map +1 -0
- package/package.json +1 -1
- package/dist/agenda-widget-BXcLo1aO.js.map +0 -1
- package/dist/agenda-widget-tZZgBGnS.cjs +0 -2
- package/dist/agenda-widget-tZZgBGnS.cjs.map +0 -1
- package/dist/use-calendar-QYGDcRjd.js.map +0 -1
- package/dist/week-view-CJeYvlpO.js.map +0 -1
- package/dist/week-view-DRGXG9E_.cjs +0 -11
- 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.
|
|
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
|
|
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
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
47
|
+
## 3. Package Exports
|
|
128
48
|
|
|
129
49
|
```jsonc
|
|
130
50
|
{
|
|
131
|
-
".":
|
|
132
|
-
"./core":
|
|
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":
|
|
135
|
-
"./lib":
|
|
136
|
-
"./utils":
|
|
137
|
-
"./styles":
|
|
138
|
-
"./styles.css": "dist/styles/index.css"
|
|
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
|
-
|
|
62
|
+
Typical consumer imports:
|
|
63
|
+
|
|
143
64
|
```tsx
|
|
144
|
-
import { InnoCalendar, type CalendarEvent } from
|
|
145
|
-
import
|
|
65
|
+
import { InnoCalendar, type CalendarEvent } from "@innosolutions/inno-calendar";
|
|
66
|
+
import "@innosolutions/inno-calendar/styles";
|
|
146
67
|
```
|
|
147
68
|
|
|
148
69
|
---
|
|
149
70
|
|
|
150
|
-
##
|
|
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;
|
|
167
|
-
hexColor?: string;
|
|
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;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
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;
|
|
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;
|
|
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 =
|
|
183
|
+
type TBadgeVariant = "dot" | "colored" | "mixed";
|
|
245
184
|
```
|
|
246
185
|
|
|
247
186
|
---
|
|
248
187
|
|
|
249
|
-
##
|
|
188
|
+
## 5. InnoCalendar Props
|
|
250
189
|
|
|
251
|
-
The main `<InnoCalendar>` component
|
|
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;
|
|
262
|
-
initialDate?: Date;
|
|
263
|
-
initialSelectedUserId?: string |
|
|
200
|
+
initialView?: TCalendarView; // default: 'week'
|
|
201
|
+
initialDate?: Date; // default: today
|
|
202
|
+
initialSelectedUserId?: string | "all";
|
|
264
203
|
initialScheduleTypeIds?: number[];
|
|
265
204
|
initialParticipantIds?: string[];
|
|
266
|
-
initialWorkingHoursView?:
|
|
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;
|
|
284
|
-
minSelectionMinutes?: number;
|
|
285
|
-
showMoreEventsInPopover?: boolean;
|
|
222
|
+
showHeader?: boolean; // default: true
|
|
223
|
+
minSelectionMinutes?: number; // default: 30
|
|
224
|
+
showMoreEventsInPopover?: boolean; // default: false
|
|
286
225
|
|
|
287
226
|
// === CUSTOM RENDERING ===
|
|
288
|
-
renderPopover?: (props: {
|
|
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;
|
|
296
|
-
filterContent?: ReactNode;
|
|
297
|
-
headerActions?: ReactNode;
|
|
237
|
+
settingsContent?: ReactNode;
|
|
238
|
+
filterContent?: ReactNode;
|
|
239
|
+
headerActions?: ReactNode;
|
|
298
240
|
}
|
|
299
241
|
```
|
|
300
242
|
|
|
301
243
|
---
|
|
302
244
|
|
|
303
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
345
|
+
## 8. EventCard Rich Data Display
|
|
404
346
|
|
|
405
|
-
The `
|
|
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
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
|
422
|
-
| 25
|
|
423
|
-
|
|
|
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
|
-
##
|
|
378
|
+
## 9. Integration Guide
|
|
430
379
|
|
|
431
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
459
|
+
For server-side filtering, build a filter panel as a regular form component and
|
|
460
|
+
pass it via `filterContent`:
|
|
487
461
|
|
|
488
|
-
```
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
<
|
|
481
|
+
<MyEventPopover event={event} onClose={onClose} />
|
|
508
482
|
)}
|
|
509
483
|
/>
|
|
510
484
|
```
|
|
511
485
|
|
|
512
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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={<
|
|
498
|
+
<InnoCalendar headerActions={<MyCreateDropdown />} />
|
|
524
499
|
```
|
|
525
500
|
|
|
526
|
-
### Slot Selection → Event
|
|
501
|
+
### 9.5 Slot Selection → Event Creation
|
|
527
502
|
|
|
528
|
-
When a user drags
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
555
|
-
|
|
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
|
-
|
|
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
|
-
|
|
533
|
+
### 9.7 Working Hours & Disabled Slots
|
|
569
534
|
|
|
570
|
-
Non-working-hour time slots
|
|
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
|
|
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
|
-
##
|
|
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
|
-
##
|
|
594
|
+
## 11. File Naming Conventions
|
|
595
|
+
|
|
596
|
+
All files use **kebab-case** naming:
|
|
629
597
|
|
|
630
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
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
|
-
##
|
|
635
|
+
## 14. Critical Rules
|
|
668
636
|
|
|
669
637
|
1. **Never add external dependencies to `core/`** — only React and native APIs
|
|
670
|
-
2.
|
|
671
|
-
3. **
|
|
672
|
-
4. **
|
|
673
|
-
5. **
|
|
674
|
-
6. **
|
|
675
|
-
7. **
|
|
676
|
-
8. **
|
|
677
|
-
9. **
|
|
678
|
-
10. **
|
|
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
|