@arquimedes.co/eureka-forms 3.0.52-new-steps → 3.0.53-new-steps
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/FormSteps/BookingStep/MaterialBookingStep/MaterialBookingStep.js +1 -0
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.d.ts +4 -0
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.js +20 -258
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.d.ts +5 -2
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.js +116 -42
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.module.css +587 -92
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.d.ts +28 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.js +117 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.d.ts +1 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.stories.test.js +49 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.d.ts +1 -0
- package/dist/Shared/ErkBookingPicker/ErkBookingPicker.test.js +107 -0
- package/dist/Shared/ErkCalendar/CalendarFunctions.d.ts +28 -0
- package/dist/Shared/ErkCalendar/CalendarFunctions.js +121 -0
- package/dist/Shared/ErkCalendar/CalendarPopovers.d.ts +45 -0
- package/dist/Shared/ErkCalendar/CalendarPopovers.js +110 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.d.ts +73 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.js +293 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.module.css +775 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.test.d.ts +1 -0
- package/dist/Shared/ErkCalendar/ErkCalendar.test.js +171 -0
- package/dist/index.lib.d.ts +4 -2
- package/dist/index.lib.js +3 -1
- package/package.json +1 -1
- package/dist/FormSteps/CalendarStep/MaterialCalendarStep/MaterialCalendarStep.module.css +0 -531
|
@@ -21,6 +21,7 @@ function MaterialBookingStep({ step, editable, fetchSlots }) {
|
|
|
21
21
|
'--eureka-primary': formStyle.primaryColor,
|
|
22
22
|
'--eureka-text': formStyle.textColor,
|
|
23
23
|
'--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
|
|
24
|
+
'--booking-accent': formStyle.secondaryContrastColor,
|
|
24
25
|
};
|
|
25
26
|
const { ref, value, onChange, error } = useFormStep(step, {
|
|
26
27
|
defaultValue: null,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
import { CalendarStepProps } from '../CalendarStep';
|
|
2
|
+
/**
|
|
3
|
+
* Conector del paso CALENDAR: enlaza el estado del formulario (`useFormStep`) y el
|
|
4
|
+
* theming del form con el componente reutilizable `ErkCalendar` (en Shared).
|
|
5
|
+
*/
|
|
2
6
|
declare function MaterialCalendarStep({ step, editable }: CalendarStepProps): JSX.Element;
|
|
3
7
|
export default MaterialCalendarStep;
|
|
@@ -1,269 +1,31 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import { useContext
|
|
3
|
-
import { startOfWeek, endOfWeek, addWeeks, addDays, addMonths, startOfMonth, endOfMonth, isSameMonth, isSameDay, isToday, format, parse, startOfDay, } from 'date-fns';
|
|
4
|
-
import { es } from 'date-fns/locale';
|
|
5
|
-
import { Popover } from '@mui/material';
|
|
6
|
-
import ArrowLeftIcon from '../../../Icons/ArrowLeftIcon';
|
|
7
|
-
import ArrowRightIcon from '../../../Icons/ArrowRightIcon';
|
|
8
|
-
import ViewWeekIcon from '../../../Icons/ViewWeekIcon';
|
|
9
|
-
import ViewDayIcon from '../../../Icons/ViewDayIcon';
|
|
10
|
-
import CalendarIcon from '../../../Icons/CalendarIcon';
|
|
11
|
-
import AddIcon from '../../../Icons/AddIcon';
|
|
12
|
-
import CloseIcon from '../../../Icons/CloseIcon';
|
|
13
|
-
import DeleteIcon from '../../../Icons/DeleteIcon';
|
|
14
|
-
import { CalendarEventCategory, CalendarDefaultView, } from '../../../@Types/CalendarFormStep';
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useContext } from 'react';
|
|
15
3
|
import { useFormStep } from '../../StepHooks';
|
|
16
4
|
import FormContext from '../../../Contexts/FormContext';
|
|
17
5
|
import { selectBreakPoint, useAppSelector } from '../../../hooks';
|
|
18
6
|
import { calcStepWidth } from '../../StepFunctions';
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const WEEK_START = 1; // Monday
|
|
25
|
-
const CATEGORY_COLORS = {
|
|
26
|
-
[CalendarEventCategory.WORKING]: '#4CAF50',
|
|
27
|
-
[CalendarEventCategory.PHYSICAL]: '#2196F3',
|
|
28
|
-
[CalendarEventCategory.VIRTUAL]: '#9C27B0',
|
|
29
|
-
[CalendarEventCategory.SPECIFIC]: '#FF9800',
|
|
30
|
-
[CalendarEventCategory.MEETING]: '#E91E63',
|
|
31
|
-
[CalendarEventCategory.BUSY]: '#607D8B',
|
|
32
|
-
[CalendarEventCategory.CUSTOM]: '#795548',
|
|
33
|
-
};
|
|
34
|
-
const CATEGORY_LABELS = {
|
|
35
|
-
[CalendarEventCategory.WORKING]: 'Trabajo',
|
|
36
|
-
[CalendarEventCategory.PHYSICAL]: 'Presencial',
|
|
37
|
-
[CalendarEventCategory.VIRTUAL]: 'Virtual',
|
|
38
|
-
[CalendarEventCategory.SPECIFIC]: 'Específico',
|
|
39
|
-
[CalendarEventCategory.MEETING]: 'Reunión',
|
|
40
|
-
[CalendarEventCategory.BUSY]: 'Ocupado',
|
|
41
|
-
[CalendarEventCategory.CUSTOM]: 'Personalizado',
|
|
42
|
-
};
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
// Helpers
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
function parseTime(hhmm) {
|
|
47
|
-
const [h, m] = hhmm.split(':').map(Number);
|
|
48
|
-
return { h: h ?? 0, m: m ?? 0 };
|
|
49
|
-
}
|
|
50
|
-
function toMinutes(hhmm) {
|
|
51
|
-
const { h, m } = parseTime(hhmm);
|
|
52
|
-
return h * 60 + m;
|
|
53
|
-
}
|
|
54
|
-
function minutesToHHMM(mins) {
|
|
55
|
-
const h = Math.floor(mins / 60);
|
|
56
|
-
const m = mins % 60;
|
|
57
|
-
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
|
58
|
-
}
|
|
59
|
-
function isoToHHMM(iso) {
|
|
60
|
-
// "2024-06-19T09:30:00" → "09:30"
|
|
61
|
-
return iso.slice(11, 16);
|
|
62
|
-
}
|
|
63
|
-
function dateAndTimeToISO(date, hhmm) {
|
|
64
|
-
const { h, m } = parseTime(hhmm);
|
|
65
|
-
const d = new Date(date);
|
|
66
|
-
d.setHours(h, m, 0, 0);
|
|
67
|
-
return d.toISOString().slice(0, 19);
|
|
68
|
-
}
|
|
69
|
-
function isoToDate(iso) {
|
|
70
|
-
// Treat as local time (no timezone shift)
|
|
71
|
-
return parse(iso.slice(0, 19), 'yyyy-MM-dd\'T\'HH:mm:ss', new Date());
|
|
72
|
-
}
|
|
73
|
-
function newEventId() {
|
|
74
|
-
return `evt-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
|
|
75
|
-
}
|
|
76
|
-
function EventBlock({ event, minHour, template, onDelete, readOnly }) {
|
|
77
|
-
const startMins = toMinutes(isoToHHMM(event.start));
|
|
78
|
-
const endMins = toMinutes(isoToHHMM(event.end));
|
|
79
|
-
const top = (startMins - minHour * 60) * (CELL_HEIGHT / 60);
|
|
80
|
-
const height = Math.max((endMins - startMins) * (CELL_HEIGHT / 60), 18);
|
|
81
|
-
const color = template?.color ?? CATEGORY_COLORS[event.category] ?? '#607D8B';
|
|
82
|
-
const label = event.title ?? template?.name ?? CATEGORY_LABELS[event.category];
|
|
83
|
-
return (_jsxs("div", { className: styles.eventBlock, style: { top, height, backgroundColor: color }, title: `${label} ${isoToHHMM(event.start)}–${isoToHHMM(event.end)}`, children: [_jsx("span", { className: styles.eventLabel, children: label }), _jsxs("span", { className: styles.eventTime, children: [isoToHHMM(event.start), "\u2013", isoToHHMM(event.end)] }), !readOnly && (_jsx("button", { type: "button", className: styles.eventDeleteBtn, onClick: (e) => { e.stopPropagation(); onDelete(event.id); }, "aria-label": "Eliminar", children: _jsx(DeleteIcon, { size: 13 }) }))] }));
|
|
84
|
-
}
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
// Main component
|
|
87
|
-
// ---------------------------------------------------------------------------
|
|
7
|
+
import ErkCalendar from '../../../Shared/ErkCalendar/ErkCalendar';
|
|
8
|
+
/**
|
|
9
|
+
* Conector del paso CALENDAR: enlaza el estado del formulario (`useFormStep`) y el
|
|
10
|
+
* theming del form con el componente reutilizable `ErkCalendar` (en Shared).
|
|
11
|
+
*/
|
|
88
12
|
function MaterialCalendarStep({ step, editable }) {
|
|
89
13
|
const currentBreakPoint = useAppSelector(selectBreakPoint);
|
|
90
|
-
const { postview } = useAppSelector((state) => state.global);
|
|
91
|
-
const
|
|
14
|
+
const { postview, formStyle } = useAppSelector((state) => state.global);
|
|
15
|
+
const formCtx = useContext(FormContext);
|
|
92
16
|
const isReadOnly = !editable || postview;
|
|
93
|
-
const widthStyle = currentBreakPoint <= step.size ? '100%' : calcStepWidth(step.size,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
const showWeekends = step.showWeekends ?? true;
|
|
103
|
-
const allowedCategories = (step.allowedCategories?.length ? step.allowedCategories : Object.values(CalendarEventCategory));
|
|
104
|
-
const defaultViewMode = (step.defaultView ?? CalendarDefaultView.WEEK);
|
|
105
|
-
// ------------------------------------------------------------------
|
|
106
|
-
// Form value
|
|
107
|
-
// ------------------------------------------------------------------
|
|
108
|
-
const { value, onChange } = useFormStep(step, {
|
|
17
|
+
const widthStyle = currentBreakPoint <= step.size ? '100%' : calcStepWidth(step.size, formCtx.size);
|
|
18
|
+
const cssVars = {
|
|
19
|
+
width: widthStyle,
|
|
20
|
+
'--eureka-outline': formStyle.outlineColor,
|
|
21
|
+
'--eureka-primary': formStyle.primaryColor,
|
|
22
|
+
'--eureka-text': formStyle.textColor,
|
|
23
|
+
'--eureka-bg': formStyle.standAloneBackgroundColor ?? '#fff',
|
|
24
|
+
};
|
|
25
|
+
const { ref, value, onChange, error } = useFormStep(step, {
|
|
109
26
|
defaultValue: null,
|
|
110
|
-
rules: {
|
|
111
|
-
required: step.required ? 'Este campo es obligatorio' : undefined,
|
|
112
|
-
},
|
|
27
|
+
rules: { required: step.required ? 'Este campo es obligatorio' : undefined },
|
|
113
28
|
});
|
|
114
|
-
|
|
115
|
-
const setEvents = useCallback((updater) => {
|
|
116
|
-
onChange({
|
|
117
|
-
type: 'CALENDAR_VALUE',
|
|
118
|
-
events: updater(events),
|
|
119
|
-
timezone: value?.timezone,
|
|
120
|
-
});
|
|
121
|
-
}, [events, value?.timezone, onChange]);
|
|
122
|
-
const deleteEvent = useCallback((id) => setEvents((prev) => prev.filter((e) => e.id !== id)), [setEvents]);
|
|
123
|
-
// ------------------------------------------------------------------
|
|
124
|
-
// Navigation state
|
|
125
|
-
// ------------------------------------------------------------------
|
|
126
|
-
const [viewMode, setViewMode] = useState(defaultViewMode);
|
|
127
|
-
const [current, setCurrent] = useState(() => startOfDay(new Date()));
|
|
128
|
-
const [createState, setCreate] = useState(null);
|
|
129
|
-
// Create form
|
|
130
|
-
const [newCategory, setNewCategory] = useState(allowedCategories[0] ?? CalendarEventCategory.WORKING);
|
|
131
|
-
const [newTitle, setNewTitle] = useState('');
|
|
132
|
-
const [newStart, setNewStart] = useState('09:00');
|
|
133
|
-
const [newEnd, setNewEnd] = useState('10:00');
|
|
134
|
-
const gridRef = useRef(null);
|
|
135
|
-
// ------------------------------------------------------------------
|
|
136
|
-
// Derived view dates
|
|
137
|
-
// ------------------------------------------------------------------
|
|
138
|
-
const weekStart = startOfWeek(current, { weekStartsOn: WEEK_START });
|
|
139
|
-
const weekEnd = endOfWeek(current, { weekStartsOn: WEEK_START });
|
|
140
|
-
const visibleDays = (() => {
|
|
141
|
-
if (viewMode === 'day')
|
|
142
|
-
return [current];
|
|
143
|
-
const days = Array.from({ length: 7 }, (_, i) => addDays(weekStart, i));
|
|
144
|
-
return showWeekends ? days : days.filter((d) => d.getDay() !== 0 && d.getDay() !== 6);
|
|
145
|
-
})();
|
|
146
|
-
// ------------------------------------------------------------------
|
|
147
|
-
// Navigate
|
|
148
|
-
// ------------------------------------------------------------------
|
|
149
|
-
const prev = () => {
|
|
150
|
-
if (viewMode === 'month')
|
|
151
|
-
setCurrent((d) => addMonths(d, -1));
|
|
152
|
-
else if (viewMode === 'week')
|
|
153
|
-
setCurrent((d) => addWeeks(d, -1));
|
|
154
|
-
else
|
|
155
|
-
setCurrent((d) => addDays(d, -1));
|
|
156
|
-
};
|
|
157
|
-
const next = () => {
|
|
158
|
-
if (viewMode === 'month')
|
|
159
|
-
setCurrent((d) => addMonths(d, 1));
|
|
160
|
-
else if (viewMode === 'week')
|
|
161
|
-
setCurrent((d) => addWeeks(d, 1));
|
|
162
|
-
else
|
|
163
|
-
setCurrent((d) => addDays(d, 1));
|
|
164
|
-
};
|
|
165
|
-
const navLabel = () => {
|
|
166
|
-
if (viewMode === 'month')
|
|
167
|
-
return format(current, 'MMMM yyyy', { locale: es });
|
|
168
|
-
if (viewMode === 'week') {
|
|
169
|
-
const s = format(weekStart, 'd MMM', { locale: es });
|
|
170
|
-
const e = format(weekEnd, 'd MMM yyyy', { locale: es });
|
|
171
|
-
return `${s} – ${e}`;
|
|
172
|
-
}
|
|
173
|
-
return format(current, 'EEEE d \'de\' MMMM yyyy', { locale: es });
|
|
174
|
-
};
|
|
175
|
-
// ------------------------------------------------------------------
|
|
176
|
-
// Events per day (for time-grid views)
|
|
177
|
-
// ------------------------------------------------------------------
|
|
178
|
-
const eventsForDay = (day) => events.filter((e) => isSameDay(isoToDate(e.start), day));
|
|
179
|
-
// ------------------------------------------------------------------
|
|
180
|
-
// Create flow
|
|
181
|
-
// ------------------------------------------------------------------
|
|
182
|
-
const handleGridClick = (e, day) => {
|
|
183
|
-
if (isReadOnly)
|
|
184
|
-
return;
|
|
185
|
-
const rect = (e.currentTarget).getBoundingClientRect();
|
|
186
|
-
const relY = e.clientY - rect.top;
|
|
187
|
-
const clickedMins = Math.round((relY / CELL_HEIGHT) * 60) * 30; // snap to 30min
|
|
188
|
-
const startMins = minHour * 60 + clickedMins;
|
|
189
|
-
const startHHMM = minutesToHHMM(Math.min(startMins, (maxHour - 1) * 60));
|
|
190
|
-
const endHHMM = minutesToHHMM(Math.min(startMins + 60, maxHour * 60));
|
|
191
|
-
setNewStart(startHHMM);
|
|
192
|
-
setNewEnd(endHHMM);
|
|
193
|
-
setNewCategory(allowedCategories[0] ?? CalendarEventCategory.WORKING);
|
|
194
|
-
setNewTitle('');
|
|
195
|
-
setCreate({ anchorEl: e.currentTarget, date: day, startMins });
|
|
196
|
-
};
|
|
197
|
-
const handleMonthDayClick = (day) => {
|
|
198
|
-
setCurrent(day);
|
|
199
|
-
setViewMode('day');
|
|
200
|
-
};
|
|
201
|
-
const handleCreate = () => {
|
|
202
|
-
if (!createState)
|
|
203
|
-
return;
|
|
204
|
-
const startISO = dateAndTimeToISO(createState.date, newStart);
|
|
205
|
-
const endISO = dateAndTimeToISO(createState.date, newEnd);
|
|
206
|
-
const template = step.eventTemplates?.find((t) => t.category === newCategory);
|
|
207
|
-
const evt = {
|
|
208
|
-
id: newEventId(),
|
|
209
|
-
category: newCategory,
|
|
210
|
-
start: startISO,
|
|
211
|
-
end: endISO,
|
|
212
|
-
title: newTitle || undefined,
|
|
213
|
-
eventTemplateId: template?.id,
|
|
214
|
-
};
|
|
215
|
-
setEvents((prev) => [...prev, evt]);
|
|
216
|
-
setCreate(null);
|
|
217
|
-
};
|
|
218
|
-
// ------------------------------------------------------------------
|
|
219
|
-
// Hour labels column
|
|
220
|
-
// ------------------------------------------------------------------
|
|
221
|
-
const hourLabels = Array.from({ length: totalHours }, (_, i) => minHour + i);
|
|
222
|
-
// ------------------------------------------------------------------
|
|
223
|
-
// Time-grid view (week / day)
|
|
224
|
-
// ------------------------------------------------------------------
|
|
225
|
-
const renderTimeGrid = () => (_jsxs("div", { className: styles.gridWrapper, children: [_jsxs("div", { className: styles.gridHeader, style: { gridTemplateColumns: `48px repeat(${visibleDays.length}, 1fr)` }, children: [_jsx("div", { className: styles.cornerCell }), visibleDays.map((day) => (_jsxs("div", { className: `${styles.dayHeader} ${isToday(day) ? styles.dayHeaderToday : ''}`, onClick: () => { setCurrent(day); setViewMode('day'); }, children: [_jsx("span", { className: styles.dayHeaderDow, children: format(day, 'EEE', { locale: es }) }), _jsx("span", { className: `${styles.dayHeaderNum} ${isToday(day) ? styles.todayCircle : ''}`, children: format(day, 'd') })] }, day.toISOString())))] }), _jsx("div", { className: styles.gridBody, ref: gridRef, children: _jsxs("div", { className: styles.gridInner, style: { gridTemplateColumns: `48px repeat(${visibleDays.length}, 1fr)` }, children: [_jsx("div", { className: styles.hourLabels, children: hourLabels.map((h) => (_jsx("div", { className: styles.hourLabel, style: { height: CELL_HEIGHT }, children: h > 0 && `${String(h).padStart(2, '0')}:00` }, h))) }), visibleDays.map((day) => (_jsxs("div", { className: styles.dayColumn, style: { height: totalHours * CELL_HEIGHT }, onClick: (e) => handleGridClick(e, day), children: [hourLabels.map((h) => (_jsx("div", { className: styles.hourLine, style: { top: (h - minHour) * CELL_HEIGHT } }, h))), eventsForDay(day).map((evt) => (_jsx(EventBlock, { event: evt, minHour: minHour, template: step.eventTemplates?.find((t) => t.category === evt.category), onDelete: deleteEvent, readOnly: isReadOnly }, evt.id)))] }, day.toISOString())))] }) })] }));
|
|
226
|
-
// ------------------------------------------------------------------
|
|
227
|
-
// Month view
|
|
228
|
-
// ------------------------------------------------------------------
|
|
229
|
-
const renderMonth = () => {
|
|
230
|
-
const mStart = startOfMonth(current);
|
|
231
|
-
const mEnd = endOfMonth(current);
|
|
232
|
-
const gridStart = startOfWeek(mStart, { weekStartsOn: WEEK_START });
|
|
233
|
-
const gridEnd = endOfWeek(mEnd, { weekStartsOn: WEEK_START });
|
|
234
|
-
const days = [];
|
|
235
|
-
let d = gridStart;
|
|
236
|
-
while (d <= gridEnd) {
|
|
237
|
-
days.push(d);
|
|
238
|
-
d = addDays(d, 1);
|
|
239
|
-
}
|
|
240
|
-
const DOW_LABELS = ['Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'];
|
|
241
|
-
return (_jsxs("div", { className: styles.monthGrid, children: [DOW_LABELS.map((lbl) => (_jsx("div", { className: styles.monthDowHeader, children: lbl }, lbl))), days.map((day) => {
|
|
242
|
-
const dayEvts = eventsForDay(day);
|
|
243
|
-
const outside = !isSameMonth(day, current);
|
|
244
|
-
return (_jsxs("div", { className: [
|
|
245
|
-
styles.monthCell,
|
|
246
|
-
outside ? styles.monthCellOutside : '',
|
|
247
|
-
isToday(day) ? styles.monthCellToday : '',
|
|
248
|
-
].filter(Boolean).join(' '), onClick: () => handleMonthDayClick(day), children: [_jsx("span", { className: `${styles.monthCellNum} ${isToday(day) ? styles.todayCircle : ''}`, children: format(day, 'd') }), dayEvts.slice(0, 3).map((evt) => {
|
|
249
|
-
const color = CATEGORY_COLORS[evt.category] ?? '#607D8B';
|
|
250
|
-
return (_jsx("div", { className: styles.monthEventDot, style: { backgroundColor: color }, children: evt.title ?? CATEGORY_LABELS[evt.category] }, evt.id));
|
|
251
|
-
}), dayEvts.length > 3 && (_jsxs("div", { className: styles.monthMoreLabel, children: ["+", dayEvts.length - 3, " m\u00E1s"] }))] }, day.toISOString()));
|
|
252
|
-
})] }));
|
|
253
|
-
};
|
|
254
|
-
// ------------------------------------------------------------------
|
|
255
|
-
// Create popover
|
|
256
|
-
// ------------------------------------------------------------------
|
|
257
|
-
const needsTitle = newCategory === CalendarEventCategory.MEETING ||
|
|
258
|
-
newCategory === CalendarEventCategory.CUSTOM;
|
|
259
|
-
const renderCreatePopover = () => {
|
|
260
|
-
if (!createState)
|
|
261
|
-
return null;
|
|
262
|
-
return (_jsxs(Popover, { open: true, anchorEl: createState.anchorEl, onClose: () => setCreate(null), anchorOrigin: { vertical: 'top', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, slotProps: { paper: { className: styles.popoverPaper } }, children: [_jsxs("div", { className: styles.popoverHeader, children: [_jsx("span", { className: styles.popoverTitle, children: "Nuevo evento" }), _jsx("button", { type: "button", className: styles.popoverClose, onClick: () => setCreate(null), children: _jsx(CloseIcon, { size: 16 }) })] }), _jsxs("div", { className: styles.popoverBody, children: [_jsx("div", { className: styles.popoverLabel, children: "Tipo" }), _jsx("div", { className: styles.categoryGrid, children: allowedCategories.map((cat) => (_jsxs("button", { type: "button", className: `${styles.categoryChip} ${newCategory === cat ? styles.categoryChipActive : ''}`, style: newCategory === cat ? { backgroundColor: CATEGORY_COLORS[cat], color: '#fff' } : {}, onClick: () => setNewCategory(cat), children: [_jsx("span", { className: styles.categoryDot, style: { backgroundColor: CATEGORY_COLORS[cat] } }), CATEGORY_LABELS[cat]] }, cat))) }), needsTitle && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.popoverLabel, children: "Nombre" }), _jsx("input", { className: styles.popoverInput, value: newTitle, onChange: (e) => setNewTitle(e.target.value), placeholder: "Ej: Standup, Visita cliente\u2026", autoFocus: true })] })), _jsxs("div", { className: styles.timeRow, children: [_jsxs("div", { children: [_jsx("div", { className: styles.popoverLabel, children: "Inicio" }), _jsx("input", { type: "time", className: styles.popoverInput, value: newStart, onChange: (e) => setNewStart(e.target.value) })] }), _jsx("span", { className: styles.timeDash, children: "\u2013" }), _jsxs("div", { children: [_jsx("div", { className: styles.popoverLabel, children: "Fin" }), _jsx("input", { type: "time", className: styles.popoverInput, value: newEnd, onChange: (e) => setNewEnd(e.target.value) })] })] }), _jsxs("button", { type: "button", className: styles.createBtn, style: { backgroundColor: CATEGORY_COLORS[newCategory] }, onClick: handleCreate, children: [_jsx(AddIcon, { size: 16 }), "Agregar bloque"] })] })] }));
|
|
263
|
-
};
|
|
264
|
-
// ------------------------------------------------------------------
|
|
265
|
-
// Render
|
|
266
|
-
// ------------------------------------------------------------------
|
|
267
|
-
return (_jsxs("div", { className: styles.container, style: { width: widthStyle }, children: [_jsxs("div", { className: styles.toolbar, children: [_jsxs("div", { className: styles.viewSwitcher, children: [_jsx("button", { type: "button", className: `${styles.viewBtn} ${viewMode === 'day' ? styles.viewBtnActive : ''}`, onClick: () => setViewMode('day'), title: "D\u00EDa", children: _jsx(ViewDayIcon, { size: 18 }) }), _jsx("button", { type: "button", className: `${styles.viewBtn} ${viewMode === 'week' ? styles.viewBtnActive : ''}`, onClick: () => setViewMode('week'), title: "Semana", children: _jsx(ViewWeekIcon, { size: 18 }) }), _jsx("button", { type: "button", className: `${styles.viewBtn} ${viewMode === 'month' ? styles.viewBtnActive : ''}`, onClick: () => setViewMode('month'), title: "Mes", children: _jsx(CalendarIcon, { size: 18 }) })] }), _jsxs("div", { className: styles.navGroup, children: [_jsx("button", { type: "button", className: styles.navBtn, onClick: prev, children: _jsx(ArrowLeftIcon, { size: 20 }) }), _jsx("span", { className: styles.navLabel, children: navLabel() }), _jsx("button", { type: "button", className: styles.navBtn, onClick: next, children: _jsx(ArrowRightIcon, { size: 20 }) })] }), events.length > 0 && (_jsxs("span", { className: styles.eventBadge, children: [events.length, " evento", events.length !== 1 ? 's' : ''] })), !isReadOnly && viewMode !== 'month' && (_jsx("span", { className: styles.hintText, children: "Clic en el grid para agregar" }))] }), _jsx("div", { className: styles.calendarCard, children: viewMode === 'month' ? renderMonth() : renderTimeGrid() }), !isReadOnly && renderCreatePopover()] }));
|
|
29
|
+
return (_jsx("div", { ref: ref, style: cssVars, children: _jsx(ErkCalendar, { value: value, onChange: onChange, defaultView: step.defaultView, enabledViews: step.enabledViews, allowedCategories: step.allowedCategories, eventTemplates: step.eventTemplates, showWeekends: step.showWeekends, minTime: step.minTime, maxTime: step.maxTime, allowRecurring: step.allowRecurring, timezone: step.timezone, readOnly: isReadOnly, error: error?.message }) }));
|
|
268
30
|
}
|
|
269
31
|
export default MaterialCalendarStep;
|
|
@@ -21,8 +21,11 @@ export interface ErkBookingPickerProps {
|
|
|
21
21
|
error?: string;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Selector de cita
|
|
25
|
-
*
|
|
24
|
+
* Selector de cita: una tarjeta de entrada ("Agendar cita") abre el wizard en un modal —
|
|
25
|
+
* una decisión por pantalla (persona → día → hora → confirmar), con panel izquierdo de
|
|
26
|
+
* resumen que se va llenando, breadcrumb navegable arriba y barra inferior Atrás / Siguiente.
|
|
27
|
+
* En el paso final el panel derecho desaparece y el resumen se expande en una columna
|
|
28
|
+
* centrada (cross-fade corto). Confirmar cierra el modal y deja el resumen compacto.
|
|
26
29
|
*/
|
|
27
30
|
declare function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bufferMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, collectContact, fetchSlots, readOnly, error, }: ErkBookingPickerProps): JSX.Element;
|
|
28
31
|
export default ErkBookingPicker;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { Dialog } from '@mui/material';
|
|
3
4
|
import { addDays, addMonths, format, isBefore, isSameDay, isSameMonth, startOfDay, startOfMonth, startOfWeek, } from 'date-fns';
|
|
4
5
|
import { es } from 'date-fns/locale';
|
|
5
6
|
import { BookingMode, PersonSelectionMode, } from '../../@Types/BookingFormStep';
|
|
7
|
+
import ErkButton from '../ErkButton/ErkButton';
|
|
6
8
|
import ErkTextField from '../ErkTextField/ErkTextField';
|
|
7
9
|
import styles from './ErkBookingPicker.module.css';
|
|
8
10
|
const WEEK_STARTS_ON = 1;
|
|
@@ -12,6 +14,26 @@ const MODE_LABEL = {
|
|
|
12
14
|
[BookingMode.VIRTUAL]: 'Virtual',
|
|
13
15
|
[BookingMode.HYBRID]: 'Híbrida',
|
|
14
16
|
};
|
|
17
|
+
const STEP_LABEL = {
|
|
18
|
+
person: 'Persona',
|
|
19
|
+
day: 'Día',
|
|
20
|
+
hour: 'Hora',
|
|
21
|
+
confirm: 'Confirmar',
|
|
22
|
+
};
|
|
23
|
+
const STEP_HINT = {
|
|
24
|
+
person: 'Elige con quién agendar',
|
|
25
|
+
day: 'Elige un día',
|
|
26
|
+
hour: 'Elige una hora libre',
|
|
27
|
+
confirm: 'Confirma tu cita',
|
|
28
|
+
};
|
|
29
|
+
/** Duración del fade al reestructurarse el layout (entrar/salir del paso final). */
|
|
30
|
+
const XFADE_MS = 160;
|
|
31
|
+
/** El CTA de confirmar usa el acento del form (secondaryContrastColor vía --booking-accent). */
|
|
32
|
+
const ACCENT_SX = {
|
|
33
|
+
backgroundColor: 'var(--booking-accent, #ee6c4d)',
|
|
34
|
+
'&:hover': { backgroundColor: 'var(--booking-accent, #ee6c4d)', filter: 'brightness(0.95)' },
|
|
35
|
+
'&:disabled': { backgroundColor: 'var(--booking-accent, #ee6c4d)', color: '#fff' },
|
|
36
|
+
};
|
|
15
37
|
function fmtDate(date) {
|
|
16
38
|
return format(date, 'yyyy-MM-dd');
|
|
17
39
|
}
|
|
@@ -31,22 +53,31 @@ function capitalize(text) {
|
|
|
31
53
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
32
54
|
}
|
|
33
55
|
/**
|
|
34
|
-
* Selector de cita
|
|
35
|
-
*
|
|
56
|
+
* Selector de cita: una tarjeta de entrada ("Agendar cita") abre el wizard en un modal —
|
|
57
|
+
* una decisión por pantalla (persona → día → hora → confirmar), con panel izquierdo de
|
|
58
|
+
* resumen que se va llenando, breadcrumb navegable arriba y barra inferior Atrás / Siguiente.
|
|
59
|
+
* En el paso final el panel derecho desaparece y el resumen se expande en una columna
|
|
60
|
+
* centrada (cross-fade corto). Confirmar cierra el modal y deja el resumen compacto.
|
|
36
61
|
*/
|
|
37
62
|
function ErkBookingPicker({ value, onChange, label, description, durationMinutes, bufferMinutes, bookingMode, temporaryLockMinutes, personSelectionMode, persons, collectContact = false, fetchSlots, readOnly = false, error, }) {
|
|
38
63
|
const needsPerson = !!personSelectionMode && personSelectionMode !== PersonSelectionMode.NONE;
|
|
39
64
|
const personsOptional = personSelectionMode === PersonSelectionMode.OPTIONAL;
|
|
40
65
|
const today = startOfDay(new Date());
|
|
66
|
+
const wizardKeys = useMemo(() => (needsPerson ? ['person', 'day', 'hour', 'confirm'] : ['day', 'hour', 'confirm']), [needsPerson]);
|
|
67
|
+
const finalIdx = wizardKeys.length - 1;
|
|
68
|
+
const [open, setOpen] = useState(false);
|
|
69
|
+
const [wizStep, setWizStep] = useState(0);
|
|
70
|
+
const [xfade, setXfade] = useState(false);
|
|
41
71
|
const [person, setPerson] = useState(undefined);
|
|
42
72
|
const [personTouched, setPersonTouched] = useState(false);
|
|
43
|
-
const [date, setDate] = useState(
|
|
73
|
+
const [date, setDate] = useState(undefined);
|
|
44
74
|
const [viewMonth, setViewMonth] = useState(() => startOfMonth(today));
|
|
45
75
|
const [slot, setSlot] = useState(undefined);
|
|
46
76
|
const [slots, setSlots] = useState([]);
|
|
47
77
|
const [loading, setLoading] = useState(false);
|
|
48
78
|
const [contactName, setContactName] = useState('');
|
|
49
79
|
const [contactEmail, setContactEmail] = useState('');
|
|
80
|
+
const reduceMotion = useMemo(() => typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches, []);
|
|
50
81
|
const monthCells = useMemo(() => {
|
|
51
82
|
const first = startOfWeek(startOfMonth(viewMonth), { weekStartsOn: WEEK_STARTS_ON });
|
|
52
83
|
return Array.from({ length: 42 }, (_, i) => addDays(first, i));
|
|
@@ -67,15 +98,41 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
|
|
|
67
98
|
}
|
|
68
99
|
}, [fetchSlots]);
|
|
69
100
|
useEffect(() => {
|
|
70
|
-
if (value)
|
|
71
|
-
return;
|
|
72
|
-
if (needsPerson && !personsOptional && !personTouched)
|
|
101
|
+
if (value || !date)
|
|
73
102
|
return;
|
|
74
103
|
void loadSlots(date, person?.id);
|
|
75
|
-
}, [value,
|
|
104
|
+
}, [value, date, person, loadSlots]);
|
|
105
|
+
// Al entrar/salir del paso final el layout se reestructura; el reacomodo
|
|
106
|
+
// ocurre mientras la tarjeta está invisible (fade corto en dos fases).
|
|
107
|
+
const goToStep = (target) => {
|
|
108
|
+
const crossesFinal = (wizStep === finalIdx) !== (target === finalIdx);
|
|
109
|
+
if (!crossesFinal || reduceMotion) {
|
|
110
|
+
setWizStep(target);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
setXfade(true);
|
|
114
|
+
window.setTimeout(() => {
|
|
115
|
+
setWizStep(target);
|
|
116
|
+
requestAnimationFrame(() => requestAnimationFrame(() => setXfade(false)));
|
|
117
|
+
}, XFADE_MS);
|
|
118
|
+
};
|
|
119
|
+
const currentKey = wizardKeys[wizStep];
|
|
120
|
+
const canAdvance = (() => {
|
|
121
|
+
switch (currentKey) {
|
|
122
|
+
case 'person':
|
|
123
|
+
return personsOptional ? personTouched : !!person;
|
|
124
|
+
case 'day':
|
|
125
|
+
return !!date;
|
|
126
|
+
case 'hour':
|
|
127
|
+
return !!slot;
|
|
128
|
+
default:
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
76
132
|
const confirm = () => {
|
|
77
|
-
if (!slot)
|
|
133
|
+
if (!slot || !date)
|
|
78
134
|
return;
|
|
135
|
+
setOpen(false);
|
|
79
136
|
onChange({
|
|
80
137
|
type: 'BOOKING_VALUE',
|
|
81
138
|
slotId: slot.id,
|
|
@@ -91,8 +148,10 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
|
|
|
91
148
|
};
|
|
92
149
|
const reset = () => {
|
|
93
150
|
onChange(null);
|
|
151
|
+
setWizStep(0);
|
|
94
152
|
setPerson(undefined);
|
|
95
153
|
setPersonTouched(false);
|
|
154
|
+
setDate(undefined);
|
|
96
155
|
setSlot(undefined);
|
|
97
156
|
setContactName('');
|
|
98
157
|
setContactEmail('');
|
|
@@ -102,9 +161,9 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
|
|
|
102
161
|
setPersonTouched(true);
|
|
103
162
|
setSlot(undefined);
|
|
104
163
|
};
|
|
105
|
-
// Read-only / already booked → compact summary
|
|
164
|
+
// Read-only / already booked → compact summary with the same card aesthetic
|
|
106
165
|
if (readOnly || value) {
|
|
107
|
-
return (_jsx("div", { className: styles.container, children: _jsxs("div", { className: styles.
|
|
166
|
+
return (_jsx("div", { className: styles.container, children: _jsxs("div", { className: styles.entry, children: [_jsx("div", { className: styles.bookedIcon, children: "\u2713" }), _jsxs("div", { className: styles.entryInfo, children: [_jsx("span", { className: styles.entryLabel, children: label }), _jsx("span", { className: styles.bookedMain, children: value
|
|
108
167
|
? [
|
|
109
168
|
value.personName,
|
|
110
169
|
capitalize(longDate(new Date(value.date + 'T00:00:00'))),
|
|
@@ -112,38 +171,53 @@ function ErkBookingPicker({ value, onChange, label, description, durationMinutes
|
|
|
112
171
|
]
|
|
113
172
|
.filter(Boolean)
|
|
114
173
|
.join(' · ')
|
|
115
|
-
: 'Sin cita' })] }), value && !readOnly &&
|
|
174
|
+
: 'Sin cita' })] }), value && !readOnly && _jsx(ErkButton, { text: "Cambiar", variant: "outlined", onClick: reset })] }) }));
|
|
116
175
|
}
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
? `${styles.
|
|
141
|
-
:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
176
|
+
const isFinal = wizStep === finalIdx;
|
|
177
|
+
const personLabel = person?.name ?? (personTouched && personsOptional ? 'Cualquiera' : undefined);
|
|
178
|
+
const dayText = date ? capitalize(longDate(date)) : undefined;
|
|
179
|
+
const hourText = slot ? `${slot.startTime} – ${slot.endTime}` : undefined;
|
|
180
|
+
const summaryRows = [
|
|
181
|
+
...(needsPerson ? [{ key: 'person', placeholder: 'Persona', text: personLabel }] : []),
|
|
182
|
+
{ key: 'day', placeholder: 'Día', text: dayText },
|
|
183
|
+
{ key: 'hour', placeholder: 'Hora', text: hourText },
|
|
184
|
+
];
|
|
185
|
+
const navParts = [];
|
|
186
|
+
if (personLabel)
|
|
187
|
+
navParts.push(_jsx("b", { children: personLabel }, "p"));
|
|
188
|
+
if (dayText)
|
|
189
|
+
navParts.push(_jsx("span", { children: dayText }, "d"));
|
|
190
|
+
if (hourText)
|
|
191
|
+
navParts.push(_jsx("span", { className: styles.navHl, children: hourText }, "h"));
|
|
192
|
+
return (_jsxs("div", { className: styles.container, children: [_jsxs("div", { className: styles.entry, children: [_jsx("div", { className: styles.avatar, children: "\uD83D\uDCC5" }), _jsxs("div", { className: styles.entryInfo, children: [_jsx("span", { className: styles.entryLabel, children: label }), _jsx("span", { className: styles.entrySub, children: description ?? 'Agenda tu cita' })] }), _jsx(ErkButton, { text: "Agendar cita", onClick: () => setOpen(true) })] }), _jsx(Dialog, { open: open, onClose: () => setOpen(false), maxWidth: "md", fullWidth: true, slotProps: { paper: { style: { borderRadius: 16, background: 'transparent', boxShadow: 'none' } } }, children: _jsxs("div", { className: `${styles.card}${isFinal ? ` ${styles.cardFinal}` : ''}${xfade ? ` ${styles.xfade}` : ''}`, children: [_jsxs("div", { className: isFinal ? `${styles.book} ${styles.bookFinal}` : styles.book, children: [_jsxs("aside", { className: styles.who, children: [_jsxs("div", { className: styles.brand, children: [_jsx("div", { className: styles.avatar, children: person ? initials(person.name) : '📅' }), _jsxs("div", { children: [_jsx("h2", { className: styles.brandName, children: person?.name ?? label }), _jsx("p", { className: styles.brandRole, children: person?.role ?? description ?? 'Agenda tu cita' })] })] }), _jsxs("div", { className: styles.meta, children: [_jsxs("div", { className: styles.row, children: [_jsx("span", { className: styles.ic, children: "\u25F7" }), durationMinutes, " min", bufferMinutes ? ` · buffer ${bufferMinutes} min` : ''] }), bookingMode && (_jsxs("div", { className: styles.row, children: [_jsx("span", { className: styles.ic, children: "\u25F1" }), _jsx("span", { className: styles.pill, children: MODE_LABEL[bookingMode] })] }))] }), _jsx("div", { className: styles.sum, "aria-live": "polite", children: summaryRows.map((r) => (_jsxs("div", { className: r.text ? `${styles.srow} ${styles.srowOn}` : styles.srow, children: [_jsx("span", { className: styles.dot, children: "\u2713" }), _jsx("span", { children: r.text ?? r.placeholder })] }, r.key))) }), isFinal && collectContact && (_jsxs("div", { className: styles.cfields, children: [_jsx(ErkTextField, { label: "Nombre", value: contactName, onChange: setContactName }), _jsx(ErkTextField, { label: "Correo", value: contactEmail, onChange: setContactEmail })] })), !!temporaryLockMinutes && (_jsxs("div", { className: styles.lock, children: ["\u23F3 Reservaremos el horario ", _jsxs("b", { children: [temporaryLockMinutes, " min"] }), " mientras confirmas."] }))] }), _jsxs("div", { className: styles.right, children: [_jsx("nav", { className: styles.stepper, "aria-label": "Progreso", children: wizardKeys.map((key, i) => (_jsxs("span", { className: styles.stepGroup, children: [_jsxs("button", { type: "button", disabled: i >= wizStep, onClick: () => goToStep(i), className: i < wizStep
|
|
193
|
+
? `${styles.stepItem} ${styles.stepDone}`
|
|
194
|
+
: i === wizStep
|
|
195
|
+
? `${styles.stepItem} ${styles.stepOn}`
|
|
196
|
+
: styles.stepItem, children: [_jsx("span", { className: styles.stepNum, children: i < wizStep ? '✓' : i + 1 }), STEP_LABEL[key]] }), i < wizardKeys.length - 1 && _jsx("span", { className: styles.stepSep })] }, key))) }), currentKey === 'person' && (_jsxs("section", { className: styles.pane, children: [_jsx("h3", { className: styles.q, children: "\u00BFCon qui\u00E9n quieres agendar?" }), _jsx("p", { className: styles.qsub, children: personsOptional
|
|
197
|
+
? 'Elige una persona o deja que el sistema asigne a quien esté disponible.'
|
|
198
|
+
: 'Elige la persona con la que quieres tu cita.' }), _jsxs("div", { className: styles.persons, children: [(persons ?? []).map((p) => (_jsxs("button", { type: "button", className: person?.id === p.id ? `${styles.pcard} ${styles.pcardOn}` : styles.pcard, onClick: () => pickPerson(p), children: [_jsx("span", { className: styles.pav, children: initials(p.name) }), _jsxs("span", { className: styles.pinfo, children: [_jsx("span", { className: styles.pn, children: p.name }), p.role && _jsx("span", { className: styles.pr, children: p.role })] }), _jsx("span", { className: styles.chk, children: "\u2713" })] }, p.id))), personsOptional && (_jsxs("button", { type: "button", className: personTouched && !person
|
|
199
|
+
? `${styles.pcard} ${styles.pcardAny} ${styles.pcardOn}`
|
|
200
|
+
: `${styles.pcard} ${styles.pcardAny}`, onClick: () => pickPerson(undefined), children: [_jsx("span", { className: styles.pav, children: "\u2726" }), _jsxs("span", { className: styles.pinfo, children: [_jsx("span", { className: styles.pn, children: "Cualquiera" }), _jsx("span", { className: styles.pr, children: "El sistema asigna" })] }), _jsx("span", { className: styles.chk, children: "\u2713" })] }))] })] }, "person")), currentKey === 'day' && (_jsxs("section", { className: styles.pane, children: [_jsx("h3", { className: styles.q, children: "Elige un d\u00EDa" }), _jsx("p", { className: styles.qsub, children: "Solo se muestran fechas a partir de hoy." }), _jsxs("div", { className: styles.monthWrap, children: [_jsxs("div", { className: styles.monthHead, children: [_jsx("span", { className: styles.monthTitle, children: capitalize(format(viewMonth, 'MMMM yyyy', { locale: es })) }), _jsxs("span", { className: styles.monthNav, children: [_jsx("button", { type: "button", "aria-label": "Mes anterior", onClick: () => setViewMonth((m) => addMonths(m, -1)), children: "\u2039" }), _jsx("button", { type: "button", "aria-label": "Mes siguiente", onClick: () => setViewMonth((m) => addMonths(m, 1)), children: "\u203A" })] })] }), _jsxs("div", { className: styles.mgrid, children: [DOW.map((d, i) => (_jsx("span", { className: styles.mdow, children: d }, i))), monthCells.map((d) => {
|
|
201
|
+
const outside = !isSameMonth(d, viewMonth);
|
|
202
|
+
const past = isBefore(d, today);
|
|
203
|
+
const selectable = !outside && !past;
|
|
204
|
+
const cls = date && isSameDay(d, date)
|
|
205
|
+
? `${styles.mday} ${styles.mdaySel}`
|
|
206
|
+
: selectable
|
|
207
|
+
? `${styles.mday} ${styles.mdayFree}`
|
|
208
|
+
: `${styles.mday} ${styles.mdayNone}`;
|
|
209
|
+
return (_jsx("button", { type: "button", disabled: !selectable, className: cls, onClick: () => {
|
|
210
|
+
setDate(d);
|
|
211
|
+
setSlot(undefined);
|
|
212
|
+
}, children: format(d, 'd') }, d.toISOString()));
|
|
213
|
+
})] })] })] }, "day")), currentKey === 'hour' && (_jsxs("section", { className: styles.pane, children: [_jsx("h3", { className: styles.q, children: "Elige una hora" }), _jsxs("p", { className: styles.qsub, children: [dayText, " \u00B7 citas de ", durationMinutes, " min"] }), loading ? (_jsx("div", { className: styles.empty, children: "Buscando horarios\u2026" })) : slots.length === 0 ? (_jsx("div", { className: styles.empty, children: "No hay horarios disponibles ese d\u00EDa." })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.slots, children: slots.map((s) => (_jsx("button", { type: "button", disabled: !s.available, className: slot?.id === s.id
|
|
214
|
+
? `${styles.slot} ${styles.slotSel}`
|
|
215
|
+
: s.available
|
|
216
|
+
? styles.slot
|
|
217
|
+
: `${styles.slot} ${styles.slotTaken}`, onClick: () => s.available && setSlot(s), children: s.startTime }, s.id))) }), _jsx("p", { className: styles.takenNote, children: "Tachados = ocupados por otro evento o cita." })] }))] }, "hour")), currentKey === 'confirm' && (_jsxs("section", { className: `${styles.pane} ${styles.paneFinal}`, children: [_jsx("h3", { className: styles.q, children: "Revisa y confirma tu cita" }), _jsx("p", { className: styles.qsub, children: collectContact
|
|
218
|
+
? 'Si todo está bien, completa tus datos y confirma.'
|
|
219
|
+
: 'Si todo está bien, confirma tu cita.' })] }, "confirm"))] })] }), _jsxs("div", { className: styles.navbar, children: [_jsx("div", { className: styles.navSummary, children: isFinal || !canAdvance || navParts.length === 0
|
|
220
|
+
? STEP_HINT[currentKey]
|
|
221
|
+
: navParts.reduce((acc, part, i) => (i === 0 ? [part] : [...acc, _jsx("span", { children: " \u00B7 " }, `s${i}`), part]), []) }), wizStep > 0 && (_jsx(ErkButton, { text: "Atr\u00E1s", variant: "outlined", onClick: () => goToStep(wizStep - 1) })), isFinal ? (_jsx(ErkButton, { text: "Confirmar cita", sx: ACCENT_SX, disabled: !slot, onClick: confirm })) : (_jsx(ErkButton, { text: "Siguiente", disabled: !canAdvance, onClick: () => goToStep(wizStep + 1) }))] })] }) }), error && _jsx("div", { className: styles.err, children: error })] }));
|
|
148
222
|
}
|
|
149
223
|
export default ErkBookingPicker;
|