mautourco-components 0.2.76 → 0.2.78

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.
@@ -1,9 +1,12 @@
1
- import React, { useState } from 'react';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import { TransferDocket } from '../../../types/docket/services.types';
2
3
  import Button, { ButtonProps } from '../../atoms/Button/Button';
4
+ import Chip from '../../atoms/Chip/Chip';
3
5
  import Divider from '../../atoms/Divider/Divider';
4
- import DropdownInput from '../../atoms/Inputs/DropdownInput/DropdownInput';
6
+ import Icon from '../../atoms/Icon/Icon';
5
7
  import { Heading, Text } from '../../atoms/Typography/Typography';
6
8
  import FeatureRow, { FeatureRowProps } from '../../molecules/FeatureRow/FeatureRow';
9
+ import VehicleSupplement, { Supplement, SupplementValue } from '../../molecules/VehicleSupplement/VehicleSupplement';
7
10
  import './CarBookingCard.css';
8
11
 
9
12
  export type CarBookingCardSize = 'small' | 'large';
@@ -40,14 +43,18 @@ export interface CarBookingCardProps {
40
43
  /** Supplement dropdown section */
41
44
  /** Whether to render the supplement section (matches Figma variants Supplement=Yes/No/-) */
42
45
  showSupplement?: boolean;
43
- /** Optional message shown in red next to the "Supplement" label (Figma: "Supplement is not available for this vehicle.") */
46
+ /** Optional message shown next to the "Supplement" label (Figma: "Supplement is not available for this vehicle.") */
44
47
  supplementMessage?: string;
48
+ /** State variant for the supplement message: 'error' applies danger/red styling, 'default' uses normal text color */
49
+ supplementMessageState?: 'default' | 'error';
45
50
  supplementLabel?: string;
46
51
  supplementPlaceholder?: string;
47
- supplementValue?: string;
48
- supplementState?: 'default' | 'loading' | 'selected' | 'error' | 'disabled';
49
- supplementOptions?: string[];
50
- onSupplementSelect?: (option: string) => void;
52
+ /** Array of available supplements */
53
+ supplements?: Supplement[];
54
+ /** Array of transfers for supplement selection */
55
+ transfers?: TransferDocket[];
56
+ /** Handler when supplements are selected */
57
+ onSupplementChange?: (values: SupplementValue[]) => void;
51
58
 
52
59
  /** Footer price */
53
60
  totalPrice: string;
@@ -56,7 +63,7 @@ export interface CarBookingCardProps {
56
63
  /** Footer CTA */
57
64
  ctaLabel: string;
58
65
  ctaButtonProps?: Omit<ButtonProps, 'children'>;
59
- onCtaClick?: ButtonProps['onClick'];
66
+ onCtaClick?: (event: React.MouseEvent<HTMLButtonElement>, supplements?: SupplementValue[]) => void;
60
67
 
61
68
  /** Readonly mode - disables interactions and shows values as text */
62
69
  readonly?: boolean;
@@ -76,12 +83,12 @@ const CarBookingCard: React.FC<CarBookingCardProps> = ({
76
83
  priceRows = [],
77
84
  showSupplement,
78
85
  supplementMessage,
86
+ supplementMessageState = 'error',
79
87
  supplementLabel = 'Supplement',
80
88
  supplementPlaceholder = 'Select a supplement',
81
- supplementValue,
82
- supplementState = 'default',
83
- supplementOptions = [],
84
- onSupplementSelect,
89
+ supplements = [],
90
+ transfers = [],
91
+ onSupplementChange,
85
92
  totalPrice,
86
93
  totalPriceLabel = 'Total price',
87
94
  ctaLabel,
@@ -92,16 +99,94 @@ const CarBookingCard: React.FC<CarBookingCardProps> = ({
92
99
  }) => {
93
100
  const [isSelected, setIsSelected] = useState(state === 'selected');
94
101
  const [isHovered, setIsHovered] = useState(false);
102
+ const [isSupplementOpen, setIsSupplementOpen] = useState(false);
103
+ const [selectedSupplements, setSelectedSupplements] = useState<SupplementValue[]>([]);
104
+ const dropdownRef = useRef<HTMLDivElement>(null);
105
+ const panelRef = useRef<HTMLDivElement>(null);
95
106
 
96
107
  const resolvedShowSupplement =
97
- showSupplement ?? Boolean(supplementLabel || supplementPlaceholder || supplementOptions.length > 0);
98
- const resolvedSupplementState =
99
- supplementMessage ? 'disabled' : supplementState;
108
+ showSupplement ?? Boolean(supplements.length > 0 && transfers.length > 0);
109
+
110
+ const hasSupplementsSelected = selectedSupplements.some((s) => s.value > 0);
111
+
112
+ // Close dropdown when clicking outside
113
+ useEffect(() => {
114
+ const handleClickOutside = (event: MouseEvent) => {
115
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
116
+ setIsSupplementOpen(false);
117
+ }
118
+ };
119
+
120
+ if (isSupplementOpen) {
121
+ document.addEventListener('mousedown', handleClickOutside);
122
+ }
123
+
124
+ return () => {
125
+ document.removeEventListener('mousedown', handleClickOutside);
126
+ };
127
+ }, [isSupplementOpen]);
128
+
129
+ // Auto-scroll to panel when opened
130
+ useEffect(() => {
131
+ if (isSupplementOpen && panelRef.current) {
132
+ // Small delay to ensure the panel is rendered
133
+ setTimeout(() => {
134
+ panelRef.current?.scrollIntoView({
135
+ behavior: 'smooth',
136
+ block: 'nearest',
137
+ });
138
+ }, 100);
139
+ }
140
+ }, [isSupplementOpen]);
141
+
142
+ const getSupplementSummary = (): string[] => {
143
+ const summary: string[] = [];
144
+ const grouped = selectedSupplements.reduce((acc, item) => {
145
+ if (item.value > 0) {
146
+ if (!acc[item.supplementName]) {
147
+ acc[item.supplementName] = 0;
148
+ }
149
+ acc[item.supplementName] += item.value;
150
+ }
151
+ return acc;
152
+ }, {} as Record<string, number>);
153
+
154
+ Object.entries(grouped).forEach(([name, count]) => {
155
+ summary.push(`${name} x${count}`);
156
+ });
157
+
158
+ return summary;
159
+ };
100
160
 
101
- // Handle CTA click: toggle between "Add to quote" and "Selected"
161
+ const handleSupplementDone = (values: SupplementValue[]) => {
162
+ setSelectedSupplements(values);
163
+ setIsSupplementOpen(false);
164
+ onSupplementChange?.(values);
165
+ };
166
+
167
+ const handleSupplementClear = () => {
168
+ setIsSupplementOpen(false);
169
+ };
170
+
171
+ const handleSupplementToggle = () => {
172
+ if (!supplementMessage) {
173
+ setIsSupplementOpen(!isSupplementOpen);
174
+ }
175
+ };
176
+
177
+ const handleRemoveSupplement = (supplementName: string) => {
178
+ const updatedValues = selectedSupplements.map((s) =>
179
+ s.supplementName === supplementName ? { ...s, value: 0 } : s
180
+ );
181
+ setSelectedSupplements(updatedValues);
182
+ onSupplementChange?.(updatedValues);
183
+ };
184
+
185
+ // Handle CTA click: toggle between "Add to quote" and "Selected" and pass supplement data
102
186
  const handleCtaClick = (e: React.MouseEvent<HTMLButtonElement>) => {
103
187
  setIsSelected(!isSelected);
104
- onCtaClick?.(e);
188
+ // Pass the selected supplements to the parent component
189
+ onCtaClick?.(e, selectedSupplements.length > 0 ? selectedSupplements : undefined);
105
190
  };
106
191
 
107
192
  // Determine button state based on selection and hover
@@ -206,31 +291,64 @@ const CarBookingCard: React.FC<CarBookingCardProps> = ({
206
291
  size="sm"
207
292
  leading="5"
208
293
  variant="regular"
209
- className="car-booking-card__supplement-message"
294
+ className={`car-booking-card__supplement-message ${supplementMessageState === 'error' ? 'car-booking-card__supplement-message--error' : ''}`.trim()}
210
295
  >
211
296
  {supplementMessage}
212
297
  </Text>
213
298
  )}
214
299
  </div>
215
- {readonly ? (
216
- <Text
217
- size="sm"
218
- leading="5"
219
- variant="regular"
220
- color={supplementValue ? "default" : "subtle"}
221
- className="car-booking-card__supplement-value"
300
+
301
+ <div className="car-booking-card__supplement-dropdown" ref={dropdownRef}>
302
+ <button
303
+ type="button"
304
+ className={`car-booking-card__supplement-trigger ${hasSupplementsSelected ? 'car-booking-card__supplement-trigger--has-value' : ''} ${supplementMessage ? 'car-booking-card__supplement-trigger--disabled' : ''}`}
305
+ onClick={handleSupplementToggle}
306
+ disabled={Boolean(supplementMessage)}
222
307
  >
223
- {supplementValue || supplementPlaceholder}
224
- </Text>
225
- ) : (
226
- <DropdownInput
227
- placeholder={supplementPlaceholder}
228
- value={supplementValue}
229
- state={resolvedSupplementState}
230
- options={supplementOptions}
231
- onSelect={onSupplementSelect}
232
- />
233
- )}
308
+ {hasSupplementsSelected ? (
309
+ <div className="car-booking-card__supplement-chips">
310
+ {getSupplementSummary().map((summary, idx) => {
311
+ const [name] = summary.split(' x');
312
+ return (
313
+ <Chip
314
+ key={idx}
315
+ label={summary}
316
+ size="sm"
317
+ color="accent"
318
+ trailingIcon="close"
319
+ onClick={(e) => {
320
+ e.stopPropagation();
321
+ handleRemoveSupplement(name);
322
+ }}
323
+ />
324
+ );
325
+ })}
326
+ </div>
327
+ ) : (
328
+ <span className="car-booking-card__supplement-placeholder">
329
+ {supplementPlaceholder}
330
+ </span>
331
+ )}
332
+ <Icon
333
+ name="chevron-down"
334
+ size="sm"
335
+ className={`car-booking-card__supplement-icon ${isSupplementOpen ? 'car-booking-card__supplement-icon--open' : ''}`}
336
+ />
337
+ </button>
338
+
339
+ {isSupplementOpen && (
340
+ <div className="car-booking-card__supplement-panel" ref={panelRef}>
341
+ <VehicleSupplement
342
+ supplements={supplements}
343
+ transfer={transfers}
344
+ initialValues={selectedSupplements}
345
+ onChange={() => {}}
346
+ onDone={handleSupplementDone}
347
+ onClear={handleSupplementClear}
348
+ />
349
+ </div>
350
+ )}
351
+ </div>
234
352
  </section>
235
353
  )}
236
354