mautourco-components 0.2.19 → 0.2.21
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/components/atoms/Icon/icons/BusIcon.js +1 -1
- package/dist/components/atoms/Icon/icons/CatamaranIcon.js +1 -1
- package/dist/components/atoms/Icon/icons/ClockIcon.d.ts +4 -0
- package/dist/components/atoms/Icon/icons/ClockIcon.js +36 -0
- package/dist/components/atoms/Icon/icons/CloseCircleIcon.js +1 -1
- package/dist/components/atoms/Icon/icons/MapIcon.js +1 -1
- package/dist/components/atoms/Icon/icons/PlusCircleIcon.js +1 -1
- package/dist/components/atoms/Icon/icons/SeaIcon.js +1 -1
- package/dist/components/atoms/Icon/icons/registry.d.ts +1 -0
- package/dist/components/atoms/Icon/icons/registry.js +2 -0
- package/dist/components/atoms/Inputs/Input/Input.d.ts +2 -1
- package/dist/components/atoms/Inputs/Input/Input.js +1 -1
- package/dist/components/atoms/Inputs/Textarea/Textarea.d.ts +3 -1
- package/dist/components/atoms/Inputs/Textarea/Textarea.js +7 -5
- package/dist/components/molecules/BookingPax/BookingPax.d.ts +7 -0
- package/dist/components/molecules/BookingPax/BookingPax.js +21 -0
- package/dist/components/molecules/BookingPax/BookingPaxAccom.d.ts +22 -0
- package/dist/components/molecules/BookingPax/BookingPaxAccom.js +61 -0
- package/dist/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.css +2090 -0
- package/dist/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.d.ts +31 -0
- package/dist/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.js +96 -0
- package/dist/components/molecules/BookingPax/BookingPaxExcursion.d.ts +14 -0
- package/dist/components/molecules/BookingPax/BookingPaxExcursion.js +31 -0
- package/dist/components/molecules/BookingPax/BookingPaxHeader.d.ts +16 -0
- package/dist/components/molecules/BookingPax/BookingPaxHeader.js +28 -0
- package/dist/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.css +2103 -0
- package/dist/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.d.ts +11 -0
- package/dist/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.js +19 -0
- package/dist/components/molecules/BookingPax/BookingPaxRemarks.d.ts +5 -0
- package/dist/components/molecules/BookingPax/BookingPaxRemarks.js +37 -0
- package/dist/components/molecules/BookingPax/BookingPaxTransfer.d.ts +18 -0
- package/dist/components/molecules/BookingPax/BookingPaxTransfer.js +40 -0
- package/dist/components/molecules/BookingPax/index.d.ts +5 -0
- package/dist/components/molecules/BookingPax/index.js +1 -0
- package/dist/components/molecules/Calendar/CalendarInput.d.ts +6 -3
- package/dist/components/molecules/Calendar/CalendarInput.js +10 -10
- package/dist/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.css +2142 -0
- package/dist/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.d.ts +11 -0
- package/dist/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.js +19 -0
- package/dist/components/molecules/DialogContentPolicy/DialogCancellationAccom.d.ts +9 -0
- package/dist/components/molecules/DialogContentPolicy/DialogCancellationAccom.js +24 -0
- package/dist/components/molecules/DialogContentPolicy/DialogCancellationExcursion.d.ts +17 -0
- package/dist/components/molecules/DialogContentPolicy/DialogCancellationExcursion.js +20 -0
- package/dist/components/molecules/DialogContentPolicy/DialogCancellationList.d.ts +11 -0
- package/dist/components/molecules/DialogContentPolicy/DialogCancellationList.js +33 -0
- package/dist/components/molecules/DialogContentPolicy/DialogContentPolicy.d.ts +11 -0
- package/dist/components/molecules/DialogContentPolicy/DialogContentPolicy.js +27 -0
- package/dist/components/molecules/DialogContentPolicy/index.d.ts +4 -0
- package/dist/components/molecules/DialogContentPolicy/index.js +1 -0
- package/dist/components/molecules/ServiceTitle/ServiceTitle.css +3 -0
- package/dist/components/molecules/ServiceTitle/ServiceTitle.d.ts +7 -1
- package/dist/components/molecules/ServiceTitle/ServiceTitle.js +4 -3
- package/dist/components/organisms/Booking/Booking.d.ts +2 -0
- package/dist/components/organisms/Booking/Booking.js +4 -0
- package/dist/components/organisms/Booking/BookingHeader.d.ts +8 -0
- package/dist/components/organisms/Booking/BookingHeader.js +17 -0
- package/dist/components/organisms/Booking/BookingPaxList.d.ts +25 -0
- package/dist/components/organisms/Booking/BookingPaxList.js +117 -0
- package/dist/components/organisms/Booking/BookingStep/BookingStep.d.ts +1 -0
- package/dist/components/organisms/Booking/BookingStep/BookingStep.js +5 -2
- package/dist/components/organisms/DateTimePicker/DateTimePicker.d.ts +6 -3
- package/dist/components/organisms/DateTimePicker/DateTimePicker.js +28 -22
- package/dist/components/organisms/DialogBookingConfirm/DialogBookingConfirm.d.ts +10 -0
- package/dist/components/organisms/DialogBookingConfirm/DialogBookingConfirm.js +17 -0
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/checkbox.js +31 -0
- package/dist/hooks/useBookingPax.d.ts +8 -0
- package/dist/hooks/useBookingPax.js +43 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +2 -1
- package/src/components/atoms/Icon/icons/BusIcon.tsx +1 -1
- package/src/components/atoms/Icon/icons/CatamaranIcon.tsx +1 -1
- package/src/components/atoms/Icon/icons/ClockIcon.tsx +46 -0
- package/src/components/atoms/Icon/icons/CloseCircleIcon.tsx +1 -1
- package/src/components/atoms/Icon/icons/MapIcon.tsx +6 -2
- package/src/components/atoms/Icon/icons/PlusCircleIcon.tsx +1 -1
- package/src/components/atoms/Icon/icons/SeaIcon.tsx +1 -1
- package/src/components/atoms/Icon/icons/registry.tsx +2 -0
- package/src/components/atoms/Inputs/Input/Input.tsx +6 -5
- package/src/components/atoms/Inputs/Textarea/Textarea.tsx +18 -4
- package/src/components/molecules/BookingPax/BookingPax.tsx +12 -0
- package/src/components/molecules/BookingPax/BookingPaxAccom.tsx +120 -0
- package/src/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.css +4 -0
- package/src/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.tsx +188 -0
- package/src/components/molecules/BookingPax/BookingPaxExcursion.tsx +77 -0
- package/src/components/molecules/BookingPax/BookingPaxHeader.tsx +47 -0
- package/src/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.css +15 -0
- package/src/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.tsx +25 -0
- package/src/components/molecules/BookingPax/BookingPaxRemarks.tsx +46 -0
- package/src/components/molecules/BookingPax/BookingPaxTransfer.tsx +121 -0
- package/src/components/molecules/BookingPax/index.ts +9 -0
- package/src/components/molecules/Calendar/CalendarInput.tsx +26 -24
- package/src/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.css +37 -0
- package/src/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.tsx +28 -0
- package/src/components/molecules/DialogContentPolicy/DialogCancellationAccom.tsx +65 -0
- package/src/components/molecules/DialogContentPolicy/DialogCancellationExcursion.tsx +59 -0
- package/src/components/molecules/DialogContentPolicy/DialogCancellationList.tsx +49 -0
- package/src/components/molecules/DialogContentPolicy/DialogContentPolicy.tsx +45 -0
- package/src/components/molecules/DialogContentPolicy/index.ts +5 -0
- package/src/components/molecules/ServiceTitle/ServiceTitle.css +1 -1
- package/src/components/molecules/ServiceTitle/ServiceTitle.tsx +25 -7
- package/src/components/organisms/Booking/Booking.tsx +4 -0
- package/src/components/organisms/Booking/BookingHeader.tsx +24 -0
- package/src/components/organisms/Booking/BookingPaxList.tsx +224 -0
- package/src/components/organisms/Booking/BookingStep/BookingStep.tsx +8 -2
- package/src/components/organisms/DateTimePicker/DateTimePicker.tsx +69 -49
- package/src/components/organisms/DialogBookingConfirm/DialogBookingConfirm.tsx +25 -0
- package/src/components/ui/checkbox.tsx +32 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { Text } from '@/src/components/atoms/Typography/Typography';
|
|
2
|
+
import { Checkbox } from '@/src/components/ui/checkbox';
|
|
3
|
+
import useBookingPax from '@/src/hooks/useBookingPax';
|
|
4
|
+
import { cn } from '@/src/lib/utils';
|
|
5
|
+
import { CheckboxProps } from '@radix-ui/react-checkbox';
|
|
6
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
|
+
import './BookingPaxClient.css';
|
|
8
|
+
|
|
9
|
+
export interface BookingPaxClientInfo {
|
|
10
|
+
firstName: string;
|
|
11
|
+
lastName: string;
|
|
12
|
+
age: number;
|
|
13
|
+
clientType: 'Adult' | 'Teen' | 'Child' | 'Infant';
|
|
14
|
+
clientId: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BookingPaxClientOptions {
|
|
18
|
+
clientId: string;
|
|
19
|
+
checked: CheckboxProps['checked'];
|
|
20
|
+
selectedIndex: number;
|
|
21
|
+
selectedPaxIndex?: number;
|
|
22
|
+
paxCount?: number;
|
|
23
|
+
selectedClientsInfoIds?: Record<string | number, string[]>;
|
|
24
|
+
selectedClients?: Record<string | number, BookingPaxClientInfo[]>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BookingPaxClientProps {
|
|
28
|
+
clients: BookingPaxClientInfo[];
|
|
29
|
+
id: string;
|
|
30
|
+
selectedClientsInfoIds?: Record<string | number, string[]>;
|
|
31
|
+
selectedIndex: number;
|
|
32
|
+
selectedPaxIndex?: number;
|
|
33
|
+
paxCount?: number;
|
|
34
|
+
isSubmitted?: boolean;
|
|
35
|
+
onPaxChange?: (options: BookingPaxClientOptions) => void;
|
|
36
|
+
onError?: (hasError: boolean) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const BookingPaxClient: React.FC<BookingPaxClientProps> = (props) => {
|
|
40
|
+
const {
|
|
41
|
+
clients,
|
|
42
|
+
id,
|
|
43
|
+
selectedClientsInfoIds: defaultSelectedClientsInfoIds,
|
|
44
|
+
selectedIndex,
|
|
45
|
+
paxCount = 0,
|
|
46
|
+
isSubmitted,
|
|
47
|
+
onPaxChange,
|
|
48
|
+
onError,
|
|
49
|
+
} = props;
|
|
50
|
+
const [selectedClientsInfoIds, setSelectedClientsInfoIds] = useState<
|
|
51
|
+
Record<string | number, string[]>
|
|
52
|
+
>({});
|
|
53
|
+
|
|
54
|
+
const { isSelectedClient } = useBookingPax(selectedClientsInfoIds);
|
|
55
|
+
|
|
56
|
+
// Calculate selected count for current index
|
|
57
|
+
const selectedCount = useMemo(
|
|
58
|
+
() => selectedClientsInfoIds[selectedIndex]?.length ?? 0,
|
|
59
|
+
[selectedClientsInfoIds, selectedIndex]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Calculate error state using useMemo for better performance
|
|
63
|
+
const hasError = useMemo(() => {
|
|
64
|
+
if (!isSubmitted || paxCount === 0) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Error if selected count is less than required pax count
|
|
69
|
+
const isInsufficientSelection = selectedCount < paxCount;
|
|
70
|
+
|
|
71
|
+
// Check if there are unselected clients
|
|
72
|
+
const hasUnselectedClients = clients.some(
|
|
73
|
+
(client) => !isSelectedClient(selectedIndex, client.clientId)
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return isInsufficientSelection && hasUnselectedClients;
|
|
77
|
+
}, [isSubmitted, paxCount, selectedCount, clients, selectedIndex, isSelectedClient]);
|
|
78
|
+
|
|
79
|
+
const handlePaxChange = useCallback(
|
|
80
|
+
(options: BookingPaxClientOptions) => {
|
|
81
|
+
setSelectedClientsInfoIds((prev) => {
|
|
82
|
+
const currentRoomSelection = prev[options.selectedIndex] || [];
|
|
83
|
+
const currentLength = currentRoomSelection.length;
|
|
84
|
+
let newState: Record<string | number, string[]>;
|
|
85
|
+
|
|
86
|
+
// If all pax are selected and trying to select another, clear and select only this one
|
|
87
|
+
if (options.checked && options.paxCount === currentLength) {
|
|
88
|
+
newState = {
|
|
89
|
+
...prev,
|
|
90
|
+
[options.selectedIndex]: [options.clientId],
|
|
91
|
+
};
|
|
92
|
+
} else if (options.checked) {
|
|
93
|
+
// Add client if checked (prevent duplicate selection)
|
|
94
|
+
if (currentRoomSelection.includes(options.clientId)) {
|
|
95
|
+
return prev; // No change needed
|
|
96
|
+
}
|
|
97
|
+
newState = {
|
|
98
|
+
...prev,
|
|
99
|
+
[options.selectedIndex]: [...currentRoomSelection, options.clientId],
|
|
100
|
+
};
|
|
101
|
+
} else {
|
|
102
|
+
// Remove client if unchecked
|
|
103
|
+
newState = {
|
|
104
|
+
...prev,
|
|
105
|
+
[options.selectedIndex]: currentRoomSelection.filter(
|
|
106
|
+
(id) => id !== options.clientId
|
|
107
|
+
),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Call callback with updated state (not stale closure)
|
|
112
|
+
onPaxChange?.({
|
|
113
|
+
...options,
|
|
114
|
+
selectedClientsInfoIds: newState,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return newState;
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
[onPaxChange, selectedClientsInfoIds]
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Sync external state prop to internal state
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (defaultSelectedClientsInfoIds !== undefined) {
|
|
126
|
+
setSelectedClientsInfoIds(defaultSelectedClientsInfoIds);
|
|
127
|
+
}
|
|
128
|
+
}, [defaultSelectedClientsInfoIds]);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
onError?.(hasError);
|
|
132
|
+
}, [hasError, onError]);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="space-y-2">
|
|
136
|
+
<Text>
|
|
137
|
+
Please confirm the pax which applies on this service ({paxCount} Pax){' '}
|
|
138
|
+
<Text color="state-error" as="span">
|
|
139
|
+
*
|
|
140
|
+
</Text>
|
|
141
|
+
</Text>
|
|
142
|
+
<div
|
|
143
|
+
className={cn('booking-pax-client', {
|
|
144
|
+
'booking-pax-accom--error': hasError,
|
|
145
|
+
})}>
|
|
146
|
+
{clients.map((client: BookingPaxClientInfo) => {
|
|
147
|
+
const isSelected = isSelectedClient(selectedIndex, client.clientId);
|
|
148
|
+
const showError = hasError && !isSelected && clients.length !== paxCount;
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div key={client.clientId}>
|
|
152
|
+
<div className="flex items-center gap-x-2">
|
|
153
|
+
<Checkbox
|
|
154
|
+
data-error={showError}
|
|
155
|
+
id={`pc-${id}-${selectedIndex}-${client.clientId}`}
|
|
156
|
+
checked={isSelected || clients.length === paxCount}
|
|
157
|
+
disabled={clients.length === paxCount}
|
|
158
|
+
onCheckedChange={(checked) => {
|
|
159
|
+
handlePaxChange({
|
|
160
|
+
clientId: client.clientId,
|
|
161
|
+
checked,
|
|
162
|
+
paxCount,
|
|
163
|
+
selectedIndex,
|
|
164
|
+
});
|
|
165
|
+
}}
|
|
166
|
+
/>
|
|
167
|
+
<label
|
|
168
|
+
htmlFor={`pc-${id}-${selectedIndex}-${client.clientId}`}
|
|
169
|
+
className={cn('cursor-pointer', {
|
|
170
|
+
'pointer-events-none': clients.length === paxCount,
|
|
171
|
+
})}>
|
|
172
|
+
<Text
|
|
173
|
+
size="sm"
|
|
174
|
+
leading="4"
|
|
175
|
+
as="span"
|
|
176
|
+
color={showError ? 'state-error' : undefined}>
|
|
177
|
+
{client.firstName} {client.lastName} ({client.clientType}
|
|
178
|
+
{client.clientType !== 'Adult' && ` - ${client.age} years old`})
|
|
179
|
+
</Text>
|
|
180
|
+
</label>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
})}
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import useBookingPax from '@/src/hooks/useBookingPax';
|
|
2
|
+
import React, { useEffect } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
BookingPaxClient,
|
|
5
|
+
BookingPaxClientInfo,
|
|
6
|
+
BookingPaxClientProps,
|
|
7
|
+
} from './BookingPaxClient/BookingPaxClient';
|
|
8
|
+
import { BookingPaxHeader } from './BookingPaxHeader';
|
|
9
|
+
import { BookingPaxLayout } from './BookingPaxLayout/BookingPaxLayout';
|
|
10
|
+
import { BookingPaxRemarks } from './BookingPaxRemarks';
|
|
11
|
+
|
|
12
|
+
export type ExcursionType = 'sea' | 'land' | 'catamaran';
|
|
13
|
+
|
|
14
|
+
export interface BookingPaxExcursionProps extends Pick<
|
|
15
|
+
BookingPaxClientProps,
|
|
16
|
+
'selectedClientsInfoIds' | 'onPaxChange'
|
|
17
|
+
> {
|
|
18
|
+
clientsInfo: BookingPaxClientInfo[];
|
|
19
|
+
name: string;
|
|
20
|
+
type: ExcursionType;
|
|
21
|
+
isSubmitted?: boolean;
|
|
22
|
+
paxCount: number;
|
|
23
|
+
maxPaxCount?: number;
|
|
24
|
+
onRemarkChange?: (value: string) => void;
|
|
25
|
+
onError?: (hasError: boolean) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const mapType: Record<BookingPaxExcursionProps['type'], string> = {
|
|
29
|
+
sea: 'Sea excursion',
|
|
30
|
+
land: 'Land excursion',
|
|
31
|
+
catamaran: 'Cruise excursion',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const BookingPaxExcursion: React.FC<BookingPaxExcursionProps> = (props) => {
|
|
35
|
+
const {
|
|
36
|
+
name,
|
|
37
|
+
type,
|
|
38
|
+
clientsInfo,
|
|
39
|
+
paxCount,
|
|
40
|
+
selectedClientsInfoIds,
|
|
41
|
+
isSubmitted,
|
|
42
|
+
onPaxChange,
|
|
43
|
+
onRemarkChange,
|
|
44
|
+
onError,
|
|
45
|
+
} = props;
|
|
46
|
+
|
|
47
|
+
const { changeOptions, onPaxOptionsChange, onSelectedClients } =
|
|
48
|
+
useBookingPax(selectedClientsInfoIds);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
onSelectedClients(clientsInfo, onPaxChange);
|
|
52
|
+
}, [changeOptions, onPaxChange]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<BookingPaxLayout
|
|
56
|
+
title="Excursion"
|
|
57
|
+
icon="map"
|
|
58
|
+
className="booking-pax-accom"
|
|
59
|
+
header={
|
|
60
|
+
<BookingPaxHeader>
|
|
61
|
+
<BookingPaxHeader.Name name={name} icon={type} location={mapType[type]} />
|
|
62
|
+
</BookingPaxHeader>
|
|
63
|
+
}>
|
|
64
|
+
<BookingPaxClient
|
|
65
|
+
id="excursion"
|
|
66
|
+
clients={clientsInfo}
|
|
67
|
+
selectedClientsInfoIds={selectedClientsInfoIds}
|
|
68
|
+
selectedIndex={0}
|
|
69
|
+
paxCount={paxCount}
|
|
70
|
+
onPaxChange={onPaxOptionsChange}
|
|
71
|
+
isSubmitted={isSubmitted}
|
|
72
|
+
onError={onError}
|
|
73
|
+
/>
|
|
74
|
+
<BookingPaxRemarks onChange={onRemarkChange} />
|
|
75
|
+
</BookingPaxLayout>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Icon, { IconName } from '../../atoms/Icon/Icon';
|
|
3
|
+
import { Text } from '../../atoms/Typography/Typography';
|
|
4
|
+
import TextWithIcon from '../TextWithIcon/TextWithIcon';
|
|
5
|
+
|
|
6
|
+
export interface BookingPaxWithNameProps {
|
|
7
|
+
name: string;
|
|
8
|
+
icon: IconName;
|
|
9
|
+
location: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BookingPaxWithLocationProps {
|
|
13
|
+
from: string;
|
|
14
|
+
to: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const BookingPaxWithName: React.FC<BookingPaxWithNameProps> = (props) => {
|
|
18
|
+
const { name, icon, location } = props;
|
|
19
|
+
return (
|
|
20
|
+
<div className="space-y-1">
|
|
21
|
+
<Text variant="bold" size="sm" leading="4">
|
|
22
|
+
{name}
|
|
23
|
+
</Text>
|
|
24
|
+
<TextWithIcon icon={icon} textLeading="4">
|
|
25
|
+
{location}
|
|
26
|
+
</TextWithIcon>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const BookingPaxWithLocation: React.FC<BookingPaxWithLocationProps> = (props) => {
|
|
32
|
+
const { from, to } = props;
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex items-center gap-x-1">
|
|
35
|
+
<Text size="sm">{from}</Text>
|
|
36
|
+
<Icon name="arrow-right-outline" size="sm" />
|
|
37
|
+
<Text size="sm">{to}</Text>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const BookingPaxHeader = (props: React.PropsWithChildren) => {
|
|
43
|
+
return <div className="booking-pax-header" {...props} />;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
BookingPaxHeader.Name = BookingPaxWithName;
|
|
47
|
+
BookingPaxHeader.Location = BookingPaxWithLocation;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.booking-pax-layout {
|
|
2
|
+
@apply p-6 space-y-8;
|
|
3
|
+
border-radius: var(--card-border-radius-default);
|
|
4
|
+
background-color: var(--card-color-background-default);
|
|
5
|
+
border: 1px solid var(--card-color-border-default);
|
|
6
|
+
max-width: 500px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.booking-pax-layout__header {
|
|
10
|
+
@apply space-y-3;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.booking-pax-layout__content {
|
|
14
|
+
@apply space-y-8;
|
|
15
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { IconName } from '@/src/components/atoms/Icon/Icon';
|
|
2
|
+
import { cn } from '@/src/lib/utils';
|
|
3
|
+
import { ServiceTitle } from '../../ServiceTitle/ServiceTitle';
|
|
4
|
+
import './BookingPaxLayout.css';
|
|
5
|
+
|
|
6
|
+
export interface BookingPaxLayoutProps {
|
|
7
|
+
title: React.ReactNode;
|
|
8
|
+
icon: IconName;
|
|
9
|
+
header?: React.ReactNode;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const BookingPaxLayout: React.FC<BookingPaxLayoutProps> = (props) => {
|
|
15
|
+
const { title, icon, header, children, className } = props;
|
|
16
|
+
return (
|
|
17
|
+
<div className={cn('booking-pax-layout', className)}>
|
|
18
|
+
<div className="booking-pax-layout__header">
|
|
19
|
+
<ServiceTitle title={title} icon={icon} />
|
|
20
|
+
{header}
|
|
21
|
+
</div>
|
|
22
|
+
<div className="booking-pax-layout__content">{children}</div>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import Textarea from '../../atoms/Inputs/Textarea/Textarea';
|
|
3
|
+
import { Text } from '../../atoms/Typography/Typography';
|
|
4
|
+
|
|
5
|
+
export interface BookingPaxRemarksProps {
|
|
6
|
+
onChange?: (value: string) => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const BookingPaxRemarks: React.FC<BookingPaxRemarksProps> = ({ onChange }) => {
|
|
10
|
+
const [remarks, setRemarks] = useState('');
|
|
11
|
+
|
|
12
|
+
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
13
|
+
|
|
14
|
+
const adjustHeight = () => {
|
|
15
|
+
const textarea = textareaRef.current;
|
|
16
|
+
if (textarea) {
|
|
17
|
+
textarea.style.height = 'auto';
|
|
18
|
+
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
adjustHeight();
|
|
24
|
+
}, [remarks]);
|
|
25
|
+
|
|
26
|
+
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
27
|
+
const value = e.target.value;
|
|
28
|
+
setRemarks(value);
|
|
29
|
+
onChange?.(value);
|
|
30
|
+
adjustHeight();
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="space-y-2">
|
|
35
|
+
<Text size="sm">Remarks</Text>
|
|
36
|
+
<Textarea
|
|
37
|
+
ref={textareaRef}
|
|
38
|
+
placeholder="Do you have any remarks?"
|
|
39
|
+
onInput={handleChange}
|
|
40
|
+
className="resize-none overflow-hidden"
|
|
41
|
+
rows={1}
|
|
42
|
+
style={{ minHeight: '44px', height: '44px', lineHeight: '1.5' }}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import useBookingPax from '@/src/hooks/useBookingPax';
|
|
2
|
+
import React, { useCallback, useEffect, useMemo } from 'react';
|
|
3
|
+
import Input from '../../atoms/Inputs/Input/Input';
|
|
4
|
+
import { Text } from '../../atoms/Typography/Typography';
|
|
5
|
+
import DateTimePicker, {
|
|
6
|
+
DateTimePickerProps,
|
|
7
|
+
} from '../../organisms/DateTimePicker/DateTimePicker';
|
|
8
|
+
import {
|
|
9
|
+
BookingPaxClient,
|
|
10
|
+
BookingPaxClientInfo,
|
|
11
|
+
BookingPaxClientProps,
|
|
12
|
+
} from './BookingPaxClient/BookingPaxClient';
|
|
13
|
+
import { BookingPaxHeader } from './BookingPaxHeader';
|
|
14
|
+
import { BookingPaxLayout } from './BookingPaxLayout/BookingPaxLayout';
|
|
15
|
+
import { BookingPaxRemarks } from './BookingPaxRemarks';
|
|
16
|
+
|
|
17
|
+
export type ExcursionType = 'sea' | 'land' | 'catamaran';
|
|
18
|
+
|
|
19
|
+
export interface BookingPaxTransferProps extends Pick<
|
|
20
|
+
BookingPaxClientProps,
|
|
21
|
+
'selectedClientsInfoIds' | 'onPaxChange'
|
|
22
|
+
> {
|
|
23
|
+
clientsInfo: BookingPaxClientInfo[];
|
|
24
|
+
type: 'ARV' | 'DEP' | 'INH';
|
|
25
|
+
from: string;
|
|
26
|
+
to: string;
|
|
27
|
+
isSubmitted?: boolean;
|
|
28
|
+
paxCount: number;
|
|
29
|
+
maxPaxCount?: number;
|
|
30
|
+
onRemarkChange?: (value: string) => void;
|
|
31
|
+
onFlightNumberChange?: (value: string) => void;
|
|
32
|
+
onTimeChange?: DateTimePickerProps['onValueChange'];
|
|
33
|
+
onError?: (hasError: boolean) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const mapType: Record<BookingPaxTransferProps['type'], string> = {
|
|
37
|
+
ARV: 'Arrival',
|
|
38
|
+
DEP: 'Departure',
|
|
39
|
+
INH: 'Inter-Hotel',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const BookingPaxTransfer: React.FC<BookingPaxTransferProps> = (props) => {
|
|
43
|
+
const {
|
|
44
|
+
from,
|
|
45
|
+
to,
|
|
46
|
+
type,
|
|
47
|
+
clientsInfo,
|
|
48
|
+
paxCount,
|
|
49
|
+
selectedClientsInfoIds,
|
|
50
|
+
isSubmitted,
|
|
51
|
+
onPaxChange,
|
|
52
|
+
onRemarkChange,
|
|
53
|
+
onTimeChange,
|
|
54
|
+
onError,
|
|
55
|
+
onFlightNumberChange,
|
|
56
|
+
} = props;
|
|
57
|
+
|
|
58
|
+
const { changeOptions, onPaxOptionsChange, onSelectedClients } =
|
|
59
|
+
useBookingPax(selectedClientsInfoIds);
|
|
60
|
+
|
|
61
|
+
const handleFlightNumberChange = useCallback(
|
|
62
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
63
|
+
onFlightNumberChange?.(e.target.value);
|
|
64
|
+
},
|
|
65
|
+
[onFlightNumberChange]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const id = useMemo(() => {
|
|
69
|
+
return `transfer-${type}-${Math.random().toString(36).substring(2, 15)}`;
|
|
70
|
+
}, [type]);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
onSelectedClients(clientsInfo, onPaxChange);
|
|
74
|
+
}, [changeOptions, onPaxChange]);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<BookingPaxLayout
|
|
78
|
+
title={
|
|
79
|
+
<>
|
|
80
|
+
Transfer{' '}
|
|
81
|
+
<Text as="span" color="accent" variant="bold" size="xl" className="italic">
|
|
82
|
+
({mapType[type]})
|
|
83
|
+
</Text>
|
|
84
|
+
</>
|
|
85
|
+
}
|
|
86
|
+
icon="car"
|
|
87
|
+
className="booking-pax-accom"
|
|
88
|
+
header={
|
|
89
|
+
<BookingPaxHeader>
|
|
90
|
+
<BookingPaxHeader.Location from={from} to={to} />
|
|
91
|
+
</BookingPaxHeader>
|
|
92
|
+
}>
|
|
93
|
+
{type === 'INH' ? (
|
|
94
|
+
<DateTimePicker
|
|
95
|
+
icon="clock"
|
|
96
|
+
mode="time"
|
|
97
|
+
onValueChange={onTimeChange}
|
|
98
|
+
placeholder="Time of Transfer"
|
|
99
|
+
/>
|
|
100
|
+
) : (
|
|
101
|
+
<Input
|
|
102
|
+
icon="arrival"
|
|
103
|
+
iconPosition="leading"
|
|
104
|
+
placeholder="Flight number"
|
|
105
|
+
onChange={handleFlightNumberChange}
|
|
106
|
+
/>
|
|
107
|
+
)}
|
|
108
|
+
<BookingPaxClient
|
|
109
|
+
id={id}
|
|
110
|
+
clients={clientsInfo}
|
|
111
|
+
selectedClientsInfoIds={selectedClientsInfoIds}
|
|
112
|
+
selectedIndex={0}
|
|
113
|
+
paxCount={paxCount}
|
|
114
|
+
isSubmitted={isSubmitted}
|
|
115
|
+
onPaxChange={onPaxOptionsChange}
|
|
116
|
+
onError={onError}
|
|
117
|
+
/>
|
|
118
|
+
<BookingPaxRemarks onChange={onRemarkChange} />
|
|
119
|
+
</BookingPaxLayout>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type { BookingPaxAccomProps, RoomData } from './BookingPaxAccom';
|
|
2
|
+
export type {
|
|
3
|
+
BookingPaxClientInfo,
|
|
4
|
+
BookingPaxClientProps,
|
|
5
|
+
} from './BookingPaxClient/BookingPaxClient';
|
|
6
|
+
export type { BookingPaxExcursionProps } from './BookingPaxExcursion';
|
|
7
|
+
export type { BookingPaxTransferProps } from './BookingPaxTransfer';
|
|
8
|
+
|
|
9
|
+
export * from './BookingPax';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState } from
|
|
2
|
-
import Icon from
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import Icon, { IconName } from '../../atoms/Icon/Icon';
|
|
3
3
|
|
|
4
4
|
export interface CalendarInputProps {
|
|
5
5
|
/** Placeholder text */
|
|
@@ -7,7 +7,7 @@ export interface CalendarInputProps {
|
|
|
7
7
|
/** Selected date value (formatted as string) */
|
|
8
8
|
value?: string;
|
|
9
9
|
/** State of the input: default, active, typing, disabled, success, error */
|
|
10
|
-
state?:
|
|
10
|
+
state?: 'default' | 'active' | 'typing' | 'disabled' | 'success' | 'error';
|
|
11
11
|
/** Helper/support text below input */
|
|
12
12
|
/** Whether the input is disabled */
|
|
13
13
|
disabled?: boolean;
|
|
@@ -26,36 +26,39 @@ export interface CalendarInputProps {
|
|
|
26
26
|
/** Whether the calendar icon has full bg*/
|
|
27
27
|
iconBGFull?: boolean;
|
|
28
28
|
/** Position of the calendar icon: left or right */
|
|
29
|
-
iconPosition?:
|
|
29
|
+
iconPosition?: 'left' | 'right';
|
|
30
30
|
/** Show chevron on the right when icon is on the left */
|
|
31
31
|
showChevron?: boolean;
|
|
32
32
|
/** Whether the calendar popover is open */
|
|
33
33
|
isOpen?: boolean;
|
|
34
|
+
/** Icon to display on the left of the input */
|
|
35
|
+
icon?: IconName;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
const CalendarInput: React.FC<CalendarInputProps> = ({
|
|
37
|
-
placeholder =
|
|
38
|
-
value =
|
|
39
|
-
state =
|
|
39
|
+
placeholder = 'Select date',
|
|
40
|
+
value = '',
|
|
41
|
+
state = 'default',
|
|
40
42
|
disabled = false,
|
|
41
43
|
onClick,
|
|
42
44
|
onCalendarClick,
|
|
43
45
|
onChange,
|
|
44
46
|
onFocus,
|
|
45
47
|
onBlur,
|
|
46
|
-
className =
|
|
48
|
+
className = '',
|
|
47
49
|
iconBGFull = true,
|
|
48
|
-
iconPosition =
|
|
50
|
+
iconPosition = 'right',
|
|
49
51
|
showChevron = false,
|
|
50
52
|
isOpen = false,
|
|
53
|
+
icon = 'calendar',
|
|
51
54
|
}) => {
|
|
52
55
|
const [isFocused, setIsFocused] = useState(false);
|
|
53
56
|
const [localValue, setLocalValue] = useState(value);
|
|
54
57
|
const [isTyping, setIsTyping] = useState(false);
|
|
55
58
|
|
|
56
|
-
const finalState = disabled ?
|
|
59
|
+
const finalState = disabled ? 'disabled' : state;
|
|
57
60
|
const displayState =
|
|
58
|
-
isFocused && isTyping ?
|
|
61
|
+
isFocused && isTyping ? 'typing' : isFocused ? 'active' : finalState;
|
|
59
62
|
|
|
60
63
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
61
64
|
const newValue = e.target.value;
|
|
@@ -89,13 +92,12 @@ const CalendarInput: React.FC<CalendarInputProps> = ({
|
|
|
89
92
|
const iconButton = (
|
|
90
93
|
<button
|
|
91
94
|
type="button"
|
|
92
|
-
className={`calendar-input__icon-button ${iconBGFull ?
|
|
95
|
+
className={`calendar-input__icon-button ${iconBGFull ? ' calendar-input__icon-button--full-bg' : ''} calendar-input__icon-button--${iconPosition}`}
|
|
93
96
|
onClick={handleCalendarClick}
|
|
94
97
|
disabled={disabled}
|
|
95
98
|
aria-label="Open calendar picker"
|
|
96
|
-
title="Select date"
|
|
97
|
-
|
|
98
|
-
<Icon name="calendar" size="sm" className="calendar-input__icon" />
|
|
99
|
+
title="Select date">
|
|
100
|
+
<Icon name={icon} size="sm" className="calendar-input__icon" />
|
|
99
101
|
</button>
|
|
100
102
|
);
|
|
101
103
|
|
|
@@ -103,10 +105,9 @@ const CalendarInput: React.FC<CalendarInputProps> = ({
|
|
|
103
105
|
<div
|
|
104
106
|
className={`calendar-input calendar-input--${displayState} calendar-input--icon-${iconPosition} ${className}`.trim()}
|
|
105
107
|
data-node-id="3425:11289"
|
|
106
|
-
onClick={onClick}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
onClick={onClick}>
|
|
109
|
+
{iconPosition === 'left' && iconButton}
|
|
110
|
+
|
|
110
111
|
<div className="calendar-input__input-wrapper">
|
|
111
112
|
<input
|
|
112
113
|
type="text"
|
|
@@ -118,16 +119,17 @@ const CalendarInput: React.FC<CalendarInputProps> = ({
|
|
|
118
119
|
onFocus={handleFocus}
|
|
119
120
|
onBlur={handleBlur}
|
|
120
121
|
readOnly={true}
|
|
121
|
-
aria-label={
|
|
122
|
+
aria-label={'Select date'}
|
|
122
123
|
/>
|
|
123
|
-
{iconPosition ===
|
|
124
|
-
<div
|
|
125
|
-
|
|
124
|
+
{iconPosition === 'left' && showChevron && (
|
|
125
|
+
<div
|
|
126
|
+
className={`calendar-input__chevron ${isOpen ? 'calendar-input__chevron--open' : ''}`}>
|
|
127
|
+
<Icon name={'chevron-down'} size="sm" />
|
|
126
128
|
</div>
|
|
127
129
|
)}
|
|
128
130
|
</div>
|
|
129
131
|
|
|
130
|
-
{iconPosition ===
|
|
132
|
+
{iconPosition === 'right' && iconButton}
|
|
131
133
|
</div>
|
|
132
134
|
);
|
|
133
135
|
};
|
package/src/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.css
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
.cancellation-layout {
|
|
2
|
+
@apply py-6 relative space-y-6;
|
|
3
|
+
&::after {
|
|
4
|
+
content: '';
|
|
5
|
+
@apply absolute bottom-0 left-0 right-0;
|
|
6
|
+
height: 1px;
|
|
7
|
+
background-color: transparent;
|
|
8
|
+
background-image: repeating-linear-gradient(
|
|
9
|
+
to right,
|
|
10
|
+
var(--divider-color-default) 0,
|
|
11
|
+
var(--divider-color-default) 8px,
|
|
12
|
+
transparent 8px,
|
|
13
|
+
transparent 16px
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
.chip__label {
|
|
17
|
+
@apply gap-x-4;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.cancellation-layout__content {
|
|
22
|
+
@apply flex gap-x-4 items-start;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.cancellation-layout__content-left {
|
|
26
|
+
@apply flex-1;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.cancellation-layout__content-right {
|
|
30
|
+
@apply overflow-hidden;
|
|
31
|
+
max-width: 150px;
|
|
32
|
+
border-radius: var(--border-radius-rounded-2xl);
|
|
33
|
+
img {
|
|
34
|
+
object-fit: cover;
|
|
35
|
+
height: 200px;
|
|
36
|
+
}
|
|
37
|
+
}
|