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.
Files changed (109) hide show
  1. package/dist/components/atoms/Icon/icons/BusIcon.js +1 -1
  2. package/dist/components/atoms/Icon/icons/CatamaranIcon.js +1 -1
  3. package/dist/components/atoms/Icon/icons/ClockIcon.d.ts +4 -0
  4. package/dist/components/atoms/Icon/icons/ClockIcon.js +36 -0
  5. package/dist/components/atoms/Icon/icons/CloseCircleIcon.js +1 -1
  6. package/dist/components/atoms/Icon/icons/MapIcon.js +1 -1
  7. package/dist/components/atoms/Icon/icons/PlusCircleIcon.js +1 -1
  8. package/dist/components/atoms/Icon/icons/SeaIcon.js +1 -1
  9. package/dist/components/atoms/Icon/icons/registry.d.ts +1 -0
  10. package/dist/components/atoms/Icon/icons/registry.js +2 -0
  11. package/dist/components/atoms/Inputs/Input/Input.d.ts +2 -1
  12. package/dist/components/atoms/Inputs/Input/Input.js +1 -1
  13. package/dist/components/atoms/Inputs/Textarea/Textarea.d.ts +3 -1
  14. package/dist/components/atoms/Inputs/Textarea/Textarea.js +7 -5
  15. package/dist/components/molecules/BookingPax/BookingPax.d.ts +7 -0
  16. package/dist/components/molecules/BookingPax/BookingPax.js +21 -0
  17. package/dist/components/molecules/BookingPax/BookingPaxAccom.d.ts +22 -0
  18. package/dist/components/molecules/BookingPax/BookingPaxAccom.js +61 -0
  19. package/dist/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.css +2090 -0
  20. package/dist/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.d.ts +31 -0
  21. package/dist/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.js +96 -0
  22. package/dist/components/molecules/BookingPax/BookingPaxExcursion.d.ts +14 -0
  23. package/dist/components/molecules/BookingPax/BookingPaxExcursion.js +31 -0
  24. package/dist/components/molecules/BookingPax/BookingPaxHeader.d.ts +16 -0
  25. package/dist/components/molecules/BookingPax/BookingPaxHeader.js +28 -0
  26. package/dist/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.css +2103 -0
  27. package/dist/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.d.ts +11 -0
  28. package/dist/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.js +19 -0
  29. package/dist/components/molecules/BookingPax/BookingPaxRemarks.d.ts +5 -0
  30. package/dist/components/molecules/BookingPax/BookingPaxRemarks.js +37 -0
  31. package/dist/components/molecules/BookingPax/BookingPaxTransfer.d.ts +18 -0
  32. package/dist/components/molecules/BookingPax/BookingPaxTransfer.js +40 -0
  33. package/dist/components/molecules/BookingPax/index.d.ts +5 -0
  34. package/dist/components/molecules/BookingPax/index.js +1 -0
  35. package/dist/components/molecules/Calendar/CalendarInput.d.ts +6 -3
  36. package/dist/components/molecules/Calendar/CalendarInput.js +10 -10
  37. package/dist/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.css +2142 -0
  38. package/dist/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.d.ts +11 -0
  39. package/dist/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.js +19 -0
  40. package/dist/components/molecules/DialogContentPolicy/DialogCancellationAccom.d.ts +9 -0
  41. package/dist/components/molecules/DialogContentPolicy/DialogCancellationAccom.js +24 -0
  42. package/dist/components/molecules/DialogContentPolicy/DialogCancellationExcursion.d.ts +17 -0
  43. package/dist/components/molecules/DialogContentPolicy/DialogCancellationExcursion.js +20 -0
  44. package/dist/components/molecules/DialogContentPolicy/DialogCancellationList.d.ts +11 -0
  45. package/dist/components/molecules/DialogContentPolicy/DialogCancellationList.js +33 -0
  46. package/dist/components/molecules/DialogContentPolicy/DialogContentPolicy.d.ts +11 -0
  47. package/dist/components/molecules/DialogContentPolicy/DialogContentPolicy.js +27 -0
  48. package/dist/components/molecules/DialogContentPolicy/index.d.ts +4 -0
  49. package/dist/components/molecules/DialogContentPolicy/index.js +1 -0
  50. package/dist/components/molecules/ServiceTitle/ServiceTitle.css +3 -0
  51. package/dist/components/molecules/ServiceTitle/ServiceTitle.d.ts +7 -1
  52. package/dist/components/molecules/ServiceTitle/ServiceTitle.js +4 -3
  53. package/dist/components/organisms/Booking/Booking.d.ts +2 -0
  54. package/dist/components/organisms/Booking/Booking.js +4 -0
  55. package/dist/components/organisms/Booking/BookingHeader.d.ts +8 -0
  56. package/dist/components/organisms/Booking/BookingHeader.js +17 -0
  57. package/dist/components/organisms/Booking/BookingPaxList.d.ts +25 -0
  58. package/dist/components/organisms/Booking/BookingPaxList.js +117 -0
  59. package/dist/components/organisms/Booking/BookingStep/BookingStep.d.ts +1 -0
  60. package/dist/components/organisms/Booking/BookingStep/BookingStep.js +5 -2
  61. package/dist/components/organisms/DateTimePicker/DateTimePicker.d.ts +6 -3
  62. package/dist/components/organisms/DateTimePicker/DateTimePicker.js +28 -22
  63. package/dist/components/organisms/DialogBookingConfirm/DialogBookingConfirm.d.ts +10 -0
  64. package/dist/components/organisms/DialogBookingConfirm/DialogBookingConfirm.js +17 -0
  65. package/dist/components/ui/checkbox.d.ts +4 -0
  66. package/dist/components/ui/checkbox.js +31 -0
  67. package/dist/hooks/useBookingPax.d.ts +8 -0
  68. package/dist/hooks/useBookingPax.js +43 -0
  69. package/dist/index.d.ts +1 -0
  70. package/dist/index.js +1 -0
  71. package/package.json +2 -1
  72. package/src/components/atoms/Icon/icons/BusIcon.tsx +1 -1
  73. package/src/components/atoms/Icon/icons/CatamaranIcon.tsx +1 -1
  74. package/src/components/atoms/Icon/icons/ClockIcon.tsx +46 -0
  75. package/src/components/atoms/Icon/icons/CloseCircleIcon.tsx +1 -1
  76. package/src/components/atoms/Icon/icons/MapIcon.tsx +6 -2
  77. package/src/components/atoms/Icon/icons/PlusCircleIcon.tsx +1 -1
  78. package/src/components/atoms/Icon/icons/SeaIcon.tsx +1 -1
  79. package/src/components/atoms/Icon/icons/registry.tsx +2 -0
  80. package/src/components/atoms/Inputs/Input/Input.tsx +6 -5
  81. package/src/components/atoms/Inputs/Textarea/Textarea.tsx +18 -4
  82. package/src/components/molecules/BookingPax/BookingPax.tsx +12 -0
  83. package/src/components/molecules/BookingPax/BookingPaxAccom.tsx +120 -0
  84. package/src/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.css +4 -0
  85. package/src/components/molecules/BookingPax/BookingPaxClient/BookingPaxClient.tsx +188 -0
  86. package/src/components/molecules/BookingPax/BookingPaxExcursion.tsx +77 -0
  87. package/src/components/molecules/BookingPax/BookingPaxHeader.tsx +47 -0
  88. package/src/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.css +15 -0
  89. package/src/components/molecules/BookingPax/BookingPaxLayout/BookingPaxLayout.tsx +25 -0
  90. package/src/components/molecules/BookingPax/BookingPaxRemarks.tsx +46 -0
  91. package/src/components/molecules/BookingPax/BookingPaxTransfer.tsx +121 -0
  92. package/src/components/molecules/BookingPax/index.ts +9 -0
  93. package/src/components/molecules/Calendar/CalendarInput.tsx +26 -24
  94. package/src/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.css +37 -0
  95. package/src/components/molecules/DialogContentPolicy/CancellationLayout/CancellationLayout.tsx +28 -0
  96. package/src/components/molecules/DialogContentPolicy/DialogCancellationAccom.tsx +65 -0
  97. package/src/components/molecules/DialogContentPolicy/DialogCancellationExcursion.tsx +59 -0
  98. package/src/components/molecules/DialogContentPolicy/DialogCancellationList.tsx +49 -0
  99. package/src/components/molecules/DialogContentPolicy/DialogContentPolicy.tsx +45 -0
  100. package/src/components/molecules/DialogContentPolicy/index.ts +5 -0
  101. package/src/components/molecules/ServiceTitle/ServiceTitle.css +1 -1
  102. package/src/components/molecules/ServiceTitle/ServiceTitle.tsx +25 -7
  103. package/src/components/organisms/Booking/Booking.tsx +4 -0
  104. package/src/components/organisms/Booking/BookingHeader.tsx +24 -0
  105. package/src/components/organisms/Booking/BookingPaxList.tsx +224 -0
  106. package/src/components/organisms/Booking/BookingStep/BookingStep.tsx +8 -2
  107. package/src/components/organisms/DateTimePicker/DateTimePicker.tsx +69 -49
  108. package/src/components/organisms/DialogBookingConfirm/DialogBookingConfirm.tsx +25 -0
  109. 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 "react";
2
- import Icon from "../../atoms/Icon/Icon";
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?: "default" | "active" | "typing" | "disabled" | "success" | "error";
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?: "left" | "right";
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 = "Select date",
38
- value = "",
39
- state = "default",
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 = "right",
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 ? "disabled" : state;
59
+ const finalState = disabled ? 'disabled' : state;
57
60
  const displayState =
58
- isFocused && isTyping ? "typing" : isFocused ? "active" : finalState;
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 ? " calendar-input__icon-button--full-bg" : ""} calendar-input__icon-button--${iconPosition}`}
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
- {iconPosition === "left" && iconButton}
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={"Select date"}
122
+ aria-label={'Select date'}
122
123
  />
123
- {iconPosition === "left" && showChevron && (
124
- <div className={`calendar-input__chevron ${isOpen ? 'calendar-input__chevron--open' : ''}`}>
125
- <Icon name={"chevron-down"} size="sm" />
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 === "right" && iconButton}
132
+ {iconPosition === 'right' && iconButton}
131
133
  </div>
132
134
  );
133
135
  };
@@ -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
+ }