@campxdev/react-blueprint 2.2.2 → 2.2.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/cjs/index.js +1 -1
- package/dist/cjs/types/src/components/Navigation/Calendar/Calendar.d.ts +3 -0
- package/dist/cjs/types/src/components/Navigation/Calendar/index.d.ts +3 -0
- package/dist/cjs/types/src/components/Navigation/Calendar/styles.d.ts +9 -0
- package/dist/cjs/types/src/components/Navigation/Calendar/types.d.ts +90 -0
- package/dist/cjs/types/src/components/Navigation/Calendar/utils.d.ts +55 -0
- package/dist/cjs/types/src/components/Navigation/export.d.ts +2 -0
- package/dist/cjs/types/src/stories/Navigation/Calendar.stories.d.ts +10 -0
- package/dist/esm/index.js +2 -2
- package/dist/esm/types/src/components/Navigation/Calendar/Calendar.d.ts +3 -0
- package/dist/esm/types/src/components/Navigation/Calendar/index.d.ts +3 -0
- package/dist/esm/types/src/components/Navigation/Calendar/styles.d.ts +9 -0
- package/dist/esm/types/src/components/Navigation/Calendar/types.d.ts +90 -0
- package/dist/esm/types/src/components/Navigation/Calendar/utils.d.ts +55 -0
- package/dist/esm/types/src/components/Navigation/export.d.ts +2 -0
- package/dist/esm/types/src/stories/Navigation/Calendar.stories.d.ts +10 -0
- package/dist/index.d.ts +151 -4
- package/package.json +6 -1
- package/src/components/Navigation/Calendar/Calendar.tsx +243 -0
- package/src/components/Navigation/Calendar/README.md +308 -0
- package/src/components/Navigation/Calendar/index.ts +3 -0
- package/src/components/Navigation/Calendar/styles.tsx +222 -0
- package/src/components/Navigation/Calendar/types.ts +120 -0
- package/src/components/Navigation/Calendar/utils.ts +265 -0
- package/src/components/Navigation/TabsContainer/TabsContainer.tsx +4 -4
- package/src/components/Navigation/export.ts +2 -0
- package/src/stories/Navigation/Calendar.stories.tsx +475 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addDays,
|
|
3
|
+
eachDayOfInterval,
|
|
4
|
+
endOfWeek,
|
|
5
|
+
format,
|
|
6
|
+
startOfWeek,
|
|
7
|
+
} from 'date-fns';
|
|
8
|
+
import { CalendarEvent } from './types';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generate recurring events based on a pattern
|
|
12
|
+
*/
|
|
13
|
+
export interface RecurringEventConfig {
|
|
14
|
+
title: string;
|
|
15
|
+
startTime: string; // HH:mm format
|
|
16
|
+
endTime: string; // HH:mm format
|
|
17
|
+
startDate: Date;
|
|
18
|
+
endDate?: Date;
|
|
19
|
+
daysOfWeek?: number[]; // 0 = Sunday, 1 = Monday, etc.
|
|
20
|
+
interval?: number; // For weekly/monthly recurring
|
|
21
|
+
backgroundColor?: string;
|
|
22
|
+
extendedProps?: { [key: string]: any };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const generateRecurringEvents = (
|
|
26
|
+
config: RecurringEventConfig,
|
|
27
|
+
maxEvents: number = 50,
|
|
28
|
+
): CalendarEvent[] => {
|
|
29
|
+
const events: CalendarEvent[] = [];
|
|
30
|
+
const {
|
|
31
|
+
title,
|
|
32
|
+
startTime,
|
|
33
|
+
endTime,
|
|
34
|
+
startDate,
|
|
35
|
+
endDate = addDays(startDate, 365), // Default to 1 year
|
|
36
|
+
daysOfWeek = [1, 2, 3, 4, 5], // Default to weekdays
|
|
37
|
+
backgroundColor,
|
|
38
|
+
extendedProps,
|
|
39
|
+
} = config;
|
|
40
|
+
|
|
41
|
+
let currentDate = new Date(startDate);
|
|
42
|
+
let eventCount = 0;
|
|
43
|
+
|
|
44
|
+
while (currentDate <= endDate && eventCount < maxEvents) {
|
|
45
|
+
if (daysOfWeek.includes(currentDate.getDay())) {
|
|
46
|
+
const eventId = `recurring-${title
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.replace(/\s+/g, '-')}-${eventCount}`;
|
|
49
|
+
const startDateTime = format(currentDate, `yyyy-MM-dd'T'${startTime}:00`);
|
|
50
|
+
const endDateTime = format(currentDate, `yyyy-MM-dd'T'${endTime}:00`);
|
|
51
|
+
|
|
52
|
+
events.push({
|
|
53
|
+
id: eventId,
|
|
54
|
+
title,
|
|
55
|
+
start: startDateTime,
|
|
56
|
+
end: endDateTime,
|
|
57
|
+
backgroundColor,
|
|
58
|
+
extendedProps,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
eventCount++;
|
|
62
|
+
}
|
|
63
|
+
currentDate = addDays(currentDate, 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return events;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Generate sample business events for demonstration
|
|
71
|
+
*/
|
|
72
|
+
export const generateBusinessEvents = (
|
|
73
|
+
startDate: Date = new Date(),
|
|
74
|
+
): CalendarEvent[] => {
|
|
75
|
+
const weekStart = startOfWeek(startDate);
|
|
76
|
+
const weekEnd = endOfWeek(startDate);
|
|
77
|
+
const weekDays = eachDayOfInterval({ start: weekStart, end: weekEnd });
|
|
78
|
+
|
|
79
|
+
const eventTemplates = [
|
|
80
|
+
{ title: 'Daily Standup', time: '09:00-09:30', color: '#1976d2' },
|
|
81
|
+
{ title: 'Team Meeting', time: '14:00-15:00', color: '#388e3c' },
|
|
82
|
+
{ title: 'Code Review', time: '10:30-11:30', color: '#f57c00' },
|
|
83
|
+
{ title: 'Sprint Planning', time: '13:00-16:00', color: '#d32f2f' },
|
|
84
|
+
{ title: 'Client Call', time: '11:00-12:00', color: '#7b1fa2' },
|
|
85
|
+
];
|
|
86
|
+
|
|
87
|
+
const events: CalendarEvent[] = [];
|
|
88
|
+
|
|
89
|
+
weekDays.forEach((day, dayIndex) => {
|
|
90
|
+
// Skip weekends for business events
|
|
91
|
+
if (day.getDay() === 0 || day.getDay() === 6) return;
|
|
92
|
+
|
|
93
|
+
// Add 1-2 random events per day
|
|
94
|
+
const numEvents = Math.floor(Math.random() * 2) + 1;
|
|
95
|
+
const shuffledTemplates = [...eventTemplates].sort(
|
|
96
|
+
() => Math.random() - 0.5,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < numEvents && i < shuffledTemplates.length; i++) {
|
|
100
|
+
const template = shuffledTemplates[i];
|
|
101
|
+
const [startTime, endTime] = template.time.split('-');
|
|
102
|
+
|
|
103
|
+
events.push({
|
|
104
|
+
id: `business-${dayIndex}-${i}`,
|
|
105
|
+
title: template.title,
|
|
106
|
+
start: format(day, `yyyy-MM-dd'T'${startTime}:00`),
|
|
107
|
+
end: format(day, `yyyy-MM-dd'T'${endTime}:00`),
|
|
108
|
+
backgroundColor: template.color,
|
|
109
|
+
extendedProps: {
|
|
110
|
+
type: 'business',
|
|
111
|
+
department: ['Engineering', 'Sales', 'Marketing'][
|
|
112
|
+
Math.floor(Math.random() * 3)
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return events;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Filter events by date range
|
|
124
|
+
*/
|
|
125
|
+
export const filterEventsByDateRange = (
|
|
126
|
+
events: CalendarEvent[],
|
|
127
|
+
startDate: Date,
|
|
128
|
+
endDate: Date,
|
|
129
|
+
): CalendarEvent[] => {
|
|
130
|
+
return events.filter((event) => {
|
|
131
|
+
const eventStart = new Date(event.start);
|
|
132
|
+
return eventStart >= startDate && eventStart <= endDate;
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Group events by date
|
|
138
|
+
*/
|
|
139
|
+
export const groupEventsByDate = (
|
|
140
|
+
events: CalendarEvent[],
|
|
141
|
+
): { [date: string]: CalendarEvent[] } => {
|
|
142
|
+
return events.reduce((groups: { [date: string]: CalendarEvent[] }, event) => {
|
|
143
|
+
const date = format(new Date(event.start), 'yyyy-MM-dd');
|
|
144
|
+
if (!groups[date]) {
|
|
145
|
+
groups[date] = [];
|
|
146
|
+
}
|
|
147
|
+
groups[date].push(event);
|
|
148
|
+
return groups;
|
|
149
|
+
}, {});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Calculate event statistics
|
|
154
|
+
*/
|
|
155
|
+
export interface EventStats {
|
|
156
|
+
totalEvents: number;
|
|
157
|
+
eventsThisWeek: number;
|
|
158
|
+
eventsThisMonth: number;
|
|
159
|
+
averageEventsPerDay: number;
|
|
160
|
+
mostBusyDay: string;
|
|
161
|
+
leastBusyDay: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const calculateEventStats = (events: CalendarEvent[]): EventStats => {
|
|
165
|
+
const now = new Date();
|
|
166
|
+
const weekStart = startOfWeek(now);
|
|
167
|
+
const weekEnd = endOfWeek(now);
|
|
168
|
+
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
169
|
+
const monthEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
|
170
|
+
|
|
171
|
+
const eventsThisWeek = filterEventsByDateRange(events, weekStart, weekEnd);
|
|
172
|
+
const eventsThisMonth = filterEventsByDateRange(events, monthStart, monthEnd);
|
|
173
|
+
const eventsByDate = groupEventsByDate(events);
|
|
174
|
+
|
|
175
|
+
const dailyCounts = Object.values(eventsByDate).map(
|
|
176
|
+
(dayEvents) => dayEvents.length,
|
|
177
|
+
);
|
|
178
|
+
const averageEventsPerDay =
|
|
179
|
+
dailyCounts.length > 0
|
|
180
|
+
? dailyCounts.reduce((sum, count) => sum + count, 0) / dailyCounts.length
|
|
181
|
+
: 0;
|
|
182
|
+
|
|
183
|
+
const sortedDates = Object.keys(eventsByDate).sort();
|
|
184
|
+
const mostBusyDate = sortedDates.reduce(
|
|
185
|
+
(max, date) =>
|
|
186
|
+
eventsByDate[date].length > eventsByDate[max]?.length ? date : max,
|
|
187
|
+
sortedDates[0],
|
|
188
|
+
);
|
|
189
|
+
const leastBusyDate = sortedDates.reduce(
|
|
190
|
+
(min, date) =>
|
|
191
|
+
eventsByDate[date].length < eventsByDate[min]?.length ? date : min,
|
|
192
|
+
sortedDates[0],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
totalEvents: events.length,
|
|
197
|
+
eventsThisWeek: eventsThisWeek.length,
|
|
198
|
+
eventsThisMonth: eventsThisMonth.length,
|
|
199
|
+
averageEventsPerDay: Math.round(averageEventsPerDay * 100) / 100,
|
|
200
|
+
mostBusyDay: mostBusyDate
|
|
201
|
+
? format(new Date(mostBusyDate), 'EEEE, MMMM do')
|
|
202
|
+
: 'N/A',
|
|
203
|
+
leastBusyDay: leastBusyDate
|
|
204
|
+
? format(new Date(leastBusyDate), 'EEEE, MMMM do')
|
|
205
|
+
: 'N/A',
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Convert events to different formats
|
|
211
|
+
*/
|
|
212
|
+
export const exportEventsToCSV = (events: CalendarEvent[]): string => {
|
|
213
|
+
const headers = [
|
|
214
|
+
'Title',
|
|
215
|
+
'Start Date',
|
|
216
|
+
'Start Time',
|
|
217
|
+
'End Date',
|
|
218
|
+
'End Time',
|
|
219
|
+
'All Day',
|
|
220
|
+
];
|
|
221
|
+
const rows = events.map((event) => [
|
|
222
|
+
event.title,
|
|
223
|
+
format(new Date(event.start), 'yyyy-MM-dd'),
|
|
224
|
+
event.allDay ? '' : format(new Date(event.start), 'HH:mm'),
|
|
225
|
+
event.end ? format(new Date(event.end), 'yyyy-MM-dd') : '',
|
|
226
|
+
event.end && !event.allDay ? format(new Date(event.end), 'HH:mm') : '',
|
|
227
|
+
event.allDay ? 'Yes' : 'No',
|
|
228
|
+
]);
|
|
229
|
+
|
|
230
|
+
return [headers, ...rows].map((row) => row.join(',')).join('\n');
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Validate event data
|
|
235
|
+
*/
|
|
236
|
+
export const validateEvent = (
|
|
237
|
+
event: Partial<CalendarEvent>,
|
|
238
|
+
): { isValid: boolean; errors: string[] } => {
|
|
239
|
+
const errors: string[] = [];
|
|
240
|
+
|
|
241
|
+
if (!event.title || event.title.trim() === '') {
|
|
242
|
+
errors.push('Event title is required');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (!event.start) {
|
|
246
|
+
errors.push('Event start date is required');
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (event.start && event.end) {
|
|
250
|
+
const start = new Date(event.start);
|
|
251
|
+
const end = new Date(event.end);
|
|
252
|
+
if (start >= end) {
|
|
253
|
+
errors.push('Event end time must be after start time');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (event.title && event.title.length > 100) {
|
|
258
|
+
errors.push('Event title must be less than 100 characters');
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
isValid: errors.length === 0,
|
|
263
|
+
errors,
|
|
264
|
+
};
|
|
265
|
+
};
|
|
@@ -17,15 +17,15 @@ export interface TabsContainerProps {
|
|
|
17
17
|
|
|
18
18
|
const TabContent = styled(Box)(({ theme }) => ({
|
|
19
19
|
width: '100%',
|
|
20
|
-
height: 'calc(100vh - 200px)',
|
|
20
|
+
// height: 'calc(100vh - 200px)',
|
|
21
21
|
overflowY: 'auto',
|
|
22
22
|
backgroundColor: theme.palette.surface.paperBackground,
|
|
23
23
|
|
|
24
24
|
'&::-webkit-scrollbar': { display: 'none' },
|
|
25
25
|
|
|
26
|
-
'@media (max-width:959px)': {
|
|
27
|
-
|
|
28
|
-
},
|
|
26
|
+
// '@media (max-width:959px)': {
|
|
27
|
+
// height: 'calc(100vh - 224px)',
|
|
28
|
+
// },
|
|
29
29
|
}));
|
|
30
30
|
|
|
31
31
|
const DynamicContent = styled(Box)(({ theme }) => ({
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export * from '../Layout/PageHeader/components/TableColumnsSelector/TableColumnsSelector';
|
|
2
2
|
export * from './Breadcrumbs/Breadcrumbs';
|
|
3
|
+
export * from './Calendar';
|
|
4
|
+
export * from './Calendar/Calendar';
|
|
3
5
|
export * from './ConfirmDialog/ConfirmDialog';
|
|
4
6
|
export * from './Dialog/Dialog';
|
|
5
7
|
export * from './DialogButton/DialogButton';
|