mautourco-components 0.2.23 → 0.2.24
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/LineIcon.d.ts +8 -0
- package/dist/components/atoms/Icon/icons/LineIcon.js +21 -0
- package/dist/components/atoms/Icon/icons/registry.d.ts +1 -0
- package/dist/components/atoms/Icon/icons/registry.js +2 -0
- package/dist/components/molecules/AccomodationDocket/AccomodationDocket.d.ts +7 -0
- package/dist/components/molecules/AccomodationDocket/AccomodationDocket.js +69 -0
- package/dist/components/molecules/AccomodationDocket/index.d.ts +2 -0
- package/dist/components/molecules/AccomodationDocket/index.js +1 -0
- package/dist/components/molecules/BookingResume/ResumeAccom/ResumeAccom.js +1 -1
- package/dist/components/molecules/BookingResume/ResumeExcursion/ResumeExcursion.js +1 -1
- package/dist/components/molecules/BookingResume/ResumeTransfer.js +1 -1
- package/dist/components/molecules/DateDisplay/DateDisplay.css +2100 -0
- package/dist/components/molecules/DateDisplay/DateDisplay.d.ts +13 -6
- package/dist/components/molecules/DateDisplay/DateDisplay.js +22 -8
- package/dist/components/molecules/DocketPrices/DocketPrices.d.ts +19 -0
- package/dist/components/molecules/DocketPrices/DocketPrices.js +31 -0
- package/dist/components/molecules/DocketPrices/index.d.ts +3 -0
- package/dist/components/molecules/DocketPrices/index.js +2 -0
- package/dist/components/molecules/ExcursionDocket/ExcursionDocket.d.ts +8 -0
- package/dist/components/molecules/ExcursionDocket/ExcursionDocket.js +30 -0
- package/dist/components/molecules/ExcursionDocket/index.d.ts +2 -0
- package/dist/components/molecules/ExcursionDocket/index.js +1 -0
- package/dist/components/molecules/LocationDropdown/LocationDropdown.js +8 -11
- package/dist/components/molecules/OtherServiceDocket/OtherServiceDocket.d.ts +8 -0
- package/dist/components/molecules/OtherServiceDocket/OtherServiceDocket.js +29 -0
- package/dist/components/molecules/OtherServiceDocket/index.d.ts +2 -0
- package/dist/components/molecules/OtherServiceDocket/index.js +1 -0
- package/dist/components/molecules/PriceDisplay/PriceDisplay.css +2101 -0
- package/dist/components/molecules/PriceDisplay/PriceDisplay.d.ts +26 -0
- package/dist/components/molecules/PriceDisplay/PriceDisplay.js +132 -0
- package/dist/components/molecules/PriceDisplay/index.d.ts +3 -0
- package/dist/components/molecules/PriceDisplay/index.js +2 -0
- package/dist/components/molecules/TransferDocket/TransferDocket.d.ts +8 -0
- package/dist/components/molecules/TransferDocket/TransferDocket.js +59 -0
- package/dist/components/molecules/TransferDocket/index.d.ts +3 -0
- package/dist/components/molecules/TransferDocket/index.js +2 -0
- package/dist/components/organisms/Docket/Docket.d.ts +126 -0
- package/dist/components/organisms/Docket/Docket.js +125 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/styles/components/molecule/accomodation-docket.css +2222 -0
- package/dist/styles/components/molecule/docket-prices.css +2095 -0
- package/dist/styles/components/molecule/excursion-docket.css +2135 -0
- package/dist/styles/components/molecule/other-service-docket.css +2114 -0
- package/dist/styles/components/molecule/transfer-docket.css +2150 -0
- package/dist/styles/components/organism/docket.css +2448 -0
- package/dist/types/docket/docket.types.d.ts +11 -0
- package/dist/types/docket/docket.types.js +1 -0
- package/dist/types/docket/services.types.d.ts +125 -0
- package/dist/types/docket/services.types.js +1 -0
- package/package.json +1 -1
- package/src/components/atoms/Icon/icons/LineIcon.tsx +31 -0
- package/src/components/atoms/Icon/icons/registry.tsx +2 -0
- package/src/components/molecules/AccomodationDocket/AccomodationDocket.tsx +224 -0
- package/src/components/molecules/AccomodationDocket/index.ts +3 -0
- package/src/components/molecules/BookingResume/ResumeAccom/ResumeAccom.tsx +1 -1
- package/src/components/molecules/BookingResume/ResumeExcursion/ResumeExcursion.tsx +1 -1
- package/src/components/molecules/BookingResume/ResumeTransfer.tsx +1 -1
- package/src/components/molecules/DateDisplay/DateDisplay.css +21 -0
- package/src/components/molecules/DateDisplay/DateDisplay.tsx +52 -24
- package/src/components/molecules/DocketPrices/DocketPrices.tsx +56 -0
- package/src/components/molecules/DocketPrices/index.ts +4 -0
- package/src/components/molecules/ExcursionDocket/ExcursionDocket.tsx +171 -0
- package/src/components/molecules/ExcursionDocket/index.ts +2 -0
- package/src/components/molecules/LocationDropdown/LocationDropdown.tsx +41 -38
- package/src/components/molecules/OtherServiceDocket/OtherServiceDocket.tsx +58 -0
- package/src/components/molecules/OtherServiceDocket/index.ts +2 -0
- package/src/components/molecules/PriceDisplay/PriceDisplay.css +24 -0
- package/src/components/molecules/PriceDisplay/PriceDisplay.tsx +179 -0
- package/src/components/molecules/PriceDisplay/index.ts +4 -0
- package/src/components/molecules/TransferDocket/TransferDocket.tsx +156 -0
- package/src/components/molecules/TransferDocket/index.ts +4 -0
- package/src/components/organisms/CarBookingCard/index.ts +1 -0
- package/src/components/organisms/Docket/Docket.tsx +456 -0
- package/src/components/organisms/SearchBarTransfer/index.ts +2 -0
- package/src/styles/components/molecule/accomodation-docket.css +117 -0
- package/src/styles/components/molecule/docket-prices.css +13 -0
- package/src/styles/components/molecule/excursion-docket.css +47 -0
- package/src/styles/components/molecule/other-service-docket.css +30 -0
- package/src/styles/components/molecule/transfer-docket.css +61 -0
- package/src/styles/components/organism/docket.css +360 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TransferDocket as TransferDocketType } from '../../../types/docket/services.types';
|
|
3
|
+
import Icon from '../../atoms/Icon/Icon';
|
|
4
|
+
import { Text } from '../../atoms/Typography/Typography';
|
|
5
|
+
import { DateDisplay } from '../DateDisplay/DateDisplay';
|
|
6
|
+
import { PriceDisplay } from '../PriceDisplay/PriceDisplay';
|
|
7
|
+
import TextWithIcon from '../TextWithIcon/TextWithIcon';
|
|
8
|
+
import '../../../styles/components/molecule/transfer-docket.css';
|
|
9
|
+
|
|
10
|
+
export interface TransferDocketProps {
|
|
11
|
+
data: TransferDocketType;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const TransferDocket: React.FC<TransferDocketProps> = ({ data }) => {
|
|
15
|
+
const {
|
|
16
|
+
TransferDate,
|
|
17
|
+
Currency,
|
|
18
|
+
TotalPrice,
|
|
19
|
+
LocationFromName,
|
|
20
|
+
LocationToName,
|
|
21
|
+
AdultCount,
|
|
22
|
+
ChildCount,
|
|
23
|
+
TeenCount,
|
|
24
|
+
InfantCount,
|
|
25
|
+
VehicleTypeName,
|
|
26
|
+
paxAge,
|
|
27
|
+
} = data;
|
|
28
|
+
|
|
29
|
+
const formatChildrenInfo = (): React.ReactNode => {
|
|
30
|
+
const totalChildren = (ChildCount || 0) + (TeenCount || 0);
|
|
31
|
+
if (totalChildren === 0) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const childrenInfo: React.ReactNode[] = [];
|
|
36
|
+
const ages = paxAge || [];
|
|
37
|
+
|
|
38
|
+
// Group children by age
|
|
39
|
+
const childGroups: Array<{ count: number; age: number }> = [];
|
|
40
|
+
|
|
41
|
+
if (ages.length > 0) {
|
|
42
|
+
// Count children by age
|
|
43
|
+
const ageCounts: Record<number, number> = {};
|
|
44
|
+
ages.forEach((age: any) => {
|
|
45
|
+
const ageNum = typeof age === 'number' ? age : parseInt(age, 10);
|
|
46
|
+
if (!isNaN(ageNum)) {
|
|
47
|
+
ageCounts[ageNum] = (ageCounts[ageNum] || 0) + 1;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
Object.entries(ageCounts).forEach(([age, count]) => {
|
|
52
|
+
childGroups.push({ count, age: parseInt(age, 10) });
|
|
53
|
+
});
|
|
54
|
+
} else {
|
|
55
|
+
// If no age info, just show total count
|
|
56
|
+
if (totalChildren > 0) {
|
|
57
|
+
childGroups.push({ count: totalChildren, age: 0 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (childGroups.length === 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<div className="transfer-docket__children">
|
|
67
|
+
{childGroups.map((group, index) => (
|
|
68
|
+
<React.Fragment key={`child-${index}`}>
|
|
69
|
+
{index > 0 && <div className="transfer-docket__divider-vertical" />}
|
|
70
|
+
<span className="transfer-docket__child-info">
|
|
71
|
+
<Text size="sm" variant="medium" color="default">
|
|
72
|
+
{group.count} Child{group.count > 1 ? 'ren' : ''}
|
|
73
|
+
</Text>
|
|
74
|
+
{group.age > 0 ? (
|
|
75
|
+
<Text
|
|
76
|
+
size="sm"
|
|
77
|
+
variant="medium"
|
|
78
|
+
className="transfer-docket__child-age">
|
|
79
|
+
{' '}
|
|
80
|
+
({group.age} y.o)
|
|
81
|
+
</Text>
|
|
82
|
+
) : null}
|
|
83
|
+
</span>
|
|
84
|
+
</React.Fragment>
|
|
85
|
+
))}
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="transfer-docket">
|
|
92
|
+
<div className="transfer-docket__header">
|
|
93
|
+
<div className="transfer-docket__title-section">
|
|
94
|
+
<div className="transfer-docket__title-bar" />
|
|
95
|
+
<Icon name="bus" size="sm" />
|
|
96
|
+
<Text variant="bold" size="sm" color="accent">
|
|
97
|
+
Transfer
|
|
98
|
+
</Text>
|
|
99
|
+
</div>
|
|
100
|
+
{TotalPrice && Currency && (
|
|
101
|
+
<PriceDisplay currency={Currency} price={TotalPrice} />
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="transfer-docket__details">
|
|
106
|
+
{TransferDate && (
|
|
107
|
+
<DateDisplay
|
|
108
|
+
date={TransferDate}
|
|
109
|
+
calendarSize="sm"
|
|
110
|
+
textSize="sm"
|
|
111
|
+
colorMode="green"
|
|
112
|
+
/>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{(LocationFromName || LocationToName) && (
|
|
116
|
+
<div className="transfer-docket__transfer-point">
|
|
117
|
+
<Text variant="medium" size="sm" color="subtle" leading="none">
|
|
118
|
+
{LocationFromName || 'From'}
|
|
119
|
+
</Text>
|
|
120
|
+
<Icon name="arrow-down-outline" size="sm" />
|
|
121
|
+
<Text variant="medium" size="sm" color="subtle" leading="none">
|
|
122
|
+
{LocationToName || 'To'}
|
|
123
|
+
</Text>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
|
|
127
|
+
<div className="transfer-docket__guests">
|
|
128
|
+
{AdultCount !== undefined && AdultCount > 0 && (
|
|
129
|
+
<TextWithIcon icon="user" textSize="sm" color="subtle">
|
|
130
|
+
{AdultCount} Adult{AdultCount > 1 ? 's' : ''}
|
|
131
|
+
</TextWithIcon>
|
|
132
|
+
)}
|
|
133
|
+
{((ChildCount && ChildCount > 0) || (TeenCount && TeenCount > 0)) ? (
|
|
134
|
+
<div className="transfer-docket__children-wrapper">
|
|
135
|
+
<Icon
|
|
136
|
+
name="user"
|
|
137
|
+
size="sm"
|
|
138
|
+
color="var(--color-text-subtle, #303642)"
|
|
139
|
+
/>
|
|
140
|
+
{formatChildrenInfo()}
|
|
141
|
+
</div>
|
|
142
|
+
) : null}
|
|
143
|
+
</div>
|
|
144
|
+
|
|
145
|
+
{VehicleTypeName && (
|
|
146
|
+
<TextWithIcon icon="car" textSize="sm" color="subtle" textLeading="none">
|
|
147
|
+
{VehicleTypeName}
|
|
148
|
+
</TextWithIcon>
|
|
149
|
+
)}
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
);
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export default TransferDocket;
|
|
156
|
+
|
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import '../../../styles/components/organism/docket.css';
|
|
3
|
+
import { Docket as DocketType } from '../../../types/docket/docket.types';
|
|
4
|
+
import {
|
|
5
|
+
AccomodationDocket as AccomodationDocketType,
|
|
6
|
+
ExcursionDocket as ExcursionDocketType,
|
|
7
|
+
OtherServiceDocket as OtherServiceDocketType,
|
|
8
|
+
ServiceDocket,
|
|
9
|
+
TransferDocket as TransferDocketType,
|
|
10
|
+
} from '../../../types/docket/services.types';
|
|
11
|
+
import Button from '../../atoms/Button/Button';
|
|
12
|
+
import Chip from '../../atoms/Chip/Chip';
|
|
13
|
+
import Icon from '../../atoms/Icon/Icon';
|
|
14
|
+
import { AccomodationDocket } from '../../molecules/AccomodationDocket/AccomodationDocket';
|
|
15
|
+
import { DocketPrices } from '../../molecules/DocketPrices/DocketPrices';
|
|
16
|
+
import { ExcursionDocket } from '../../molecules/ExcursionDocket/ExcursionDocket';
|
|
17
|
+
import { OtherServiceDocket } from '../../molecules/OtherServiceDocket/OtherServiceDocket';
|
|
18
|
+
import { TransferDocket } from '../../molecules/TransferDocket/TransferDocket';
|
|
19
|
+
|
|
20
|
+
export interface DocketHeaderProps {
|
|
21
|
+
/**
|
|
22
|
+
* Title text for the docket header
|
|
23
|
+
*/
|
|
24
|
+
title?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Handler for the "More options" button click
|
|
28
|
+
*/
|
|
29
|
+
onMoreOptionsClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Additional CSS classes
|
|
33
|
+
*/
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DocketFooterProps {
|
|
38
|
+
/**
|
|
39
|
+
* Mode of the docket: 'single' shows only Save and Book now, 'multiple' shows all buttons
|
|
40
|
+
*/
|
|
41
|
+
mode?: 'single' | 'multiple';
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Handler for "Add new quote" button click
|
|
45
|
+
*/
|
|
46
|
+
onAddNewQuoteClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handler for "View" button click
|
|
50
|
+
*/
|
|
51
|
+
onViewClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Handler for "Save" button click
|
|
55
|
+
*/
|
|
56
|
+
onSaveClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handler for "Book now" button click
|
|
60
|
+
*/
|
|
61
|
+
onBookNowClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Additional CSS classes
|
|
65
|
+
*/
|
|
66
|
+
className?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface DocketProps {
|
|
70
|
+
/**
|
|
71
|
+
* Array of nested Docket objects
|
|
72
|
+
*/
|
|
73
|
+
dockets?: DocketType[];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Title text for the docket header
|
|
77
|
+
*/
|
|
78
|
+
title?: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handler for the "More options" button click
|
|
82
|
+
*/
|
|
83
|
+
onMoreOptionsClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Handler for "Add new quote" button click
|
|
87
|
+
*/
|
|
88
|
+
onAddNewQuoteClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Handler for "View" button click
|
|
92
|
+
*/
|
|
93
|
+
onViewClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Handler for "Save" button click
|
|
97
|
+
*/
|
|
98
|
+
onSaveClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Handler for "Book now" button click
|
|
102
|
+
*/
|
|
103
|
+
onBookNowClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Mode of the docket: 'single' shows only Save and Book now in footer, 'multiple' shows all buttons
|
|
107
|
+
*/
|
|
108
|
+
mode?: 'single' | 'multiple';
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Show the header section
|
|
112
|
+
*/
|
|
113
|
+
showHeader?: boolean;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Show the footer section
|
|
117
|
+
*/
|
|
118
|
+
showFooter?: boolean;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Additional CSS classes to apply to the docket container
|
|
122
|
+
*/
|
|
123
|
+
className?: string;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Optional click handler
|
|
127
|
+
*/
|
|
128
|
+
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Optional data attributes for testing or tracking
|
|
132
|
+
*/
|
|
133
|
+
'data-testid'?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Collapsed header component for mobile/tablet view
|
|
138
|
+
*/
|
|
139
|
+
export const DocketCollapsedHeader: React.FC<{
|
|
140
|
+
title?: string;
|
|
141
|
+
onClick?: () => void;
|
|
142
|
+
className?: string;
|
|
143
|
+
}> = ({ title = 'Your quotation', onClick, className = '' }) => {
|
|
144
|
+
return (
|
|
145
|
+
<div
|
|
146
|
+
className={`docket__collapsed-header ${className}`}
|
|
147
|
+
onClick={onClick}
|
|
148
|
+
role="button"
|
|
149
|
+
tabIndex={0}
|
|
150
|
+
onKeyDown={(e) => {
|
|
151
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
onClick?.();
|
|
154
|
+
}
|
|
155
|
+
}}>
|
|
156
|
+
<div className="docket__collapsed-header-title">
|
|
157
|
+
<Icon name="quotation" size="lg" />
|
|
158
|
+
<h2 className="docket__collapsed-header-title-text">{title}</h2>
|
|
159
|
+
</div>
|
|
160
|
+
<Icon name="eye" size="lg" />
|
|
161
|
+
</div>
|
|
162
|
+
);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Empty state component for docket when no content is provided
|
|
167
|
+
*/
|
|
168
|
+
export const DocketEmptyState: React.FC<{ className?: string }> = ({
|
|
169
|
+
className = '',
|
|
170
|
+
}) => {
|
|
171
|
+
return (
|
|
172
|
+
<div className={`docket__empty-state ${className}`}>
|
|
173
|
+
<div className="docket__empty-state-content">
|
|
174
|
+
<div className="docket__empty-state-illustration">
|
|
175
|
+
<img
|
|
176
|
+
src="/images/docket-empty-illustration.svg"
|
|
177
|
+
alt="Empty docket illustration"
|
|
178
|
+
className="docket__empty-state-image"
|
|
179
|
+
/>
|
|
180
|
+
</div>
|
|
181
|
+
<p className="docket__empty-state-text">
|
|
182
|
+
<span className="docket__empty-state-text-bold">
|
|
183
|
+
Start by adding a service here—
|
|
184
|
+
</span>
|
|
185
|
+
<span className="docket__empty-state-text-regular">
|
|
186
|
+
you'll be able to create and compare multiple quotations.
|
|
187
|
+
</span>
|
|
188
|
+
</p>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* DocketHeader component for the docket title and more options button
|
|
196
|
+
*/
|
|
197
|
+
export const DocketHeader: React.FC<DocketHeaderProps> = ({
|
|
198
|
+
title = 'Your quotation',
|
|
199
|
+
onMoreOptionsClick,
|
|
200
|
+
className = '',
|
|
201
|
+
}) => {
|
|
202
|
+
return (
|
|
203
|
+
<div className={`docket__header ${className}`}>
|
|
204
|
+
<div className="docket__header-title">
|
|
205
|
+
<Icon name="quotation" size="lg" />
|
|
206
|
+
<h2 className="docket__header-title-text">{title}</h2>
|
|
207
|
+
</div>
|
|
208
|
+
{onMoreOptionsClick && (
|
|
209
|
+
<Chip
|
|
210
|
+
onClick={onMoreOptionsClick}
|
|
211
|
+
trailingIcon="chevron-down"
|
|
212
|
+
className="docket__header-more-options">
|
|
213
|
+
More options
|
|
214
|
+
</Chip>
|
|
215
|
+
)}
|
|
216
|
+
</div>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* DocketFooter component for action buttons
|
|
222
|
+
*/
|
|
223
|
+
export const DocketFooter: React.FC<DocketFooterProps> = ({
|
|
224
|
+
mode = 'multiple',
|
|
225
|
+
onAddNewQuoteClick,
|
|
226
|
+
onViewClick,
|
|
227
|
+
onSaveClick,
|
|
228
|
+
onBookNowClick,
|
|
229
|
+
className = '',
|
|
230
|
+
}) => {
|
|
231
|
+
const isSingleMode = mode === 'single';
|
|
232
|
+
|
|
233
|
+
return (
|
|
234
|
+
<div className={`docket__footer ${className}`}>
|
|
235
|
+
{!isSingleMode && onAddNewQuoteClick && (
|
|
236
|
+
<Button
|
|
237
|
+
variant="outline-secondary"
|
|
238
|
+
size="sm"
|
|
239
|
+
leadingIcon="plus"
|
|
240
|
+
onClick={onAddNewQuoteClick}
|
|
241
|
+
className="docket__footer-button docket__footer-button--full">
|
|
242
|
+
Add new quote
|
|
243
|
+
</Button>
|
|
244
|
+
)}
|
|
245
|
+
<div className="docket__footer-actions">
|
|
246
|
+
{!isSingleMode && onViewClick && (
|
|
247
|
+
<Button
|
|
248
|
+
variant="outline-secondary"
|
|
249
|
+
size="sm"
|
|
250
|
+
onClick={onViewClick}
|
|
251
|
+
className="docket__footer-button">
|
|
252
|
+
View
|
|
253
|
+
</Button>
|
|
254
|
+
)}
|
|
255
|
+
{onSaveClick && (
|
|
256
|
+
<Button
|
|
257
|
+
variant="outline-secondary"
|
|
258
|
+
size="sm"
|
|
259
|
+
onClick={onSaveClick}
|
|
260
|
+
className="docket__footer-button">
|
|
261
|
+
Save
|
|
262
|
+
</Button>
|
|
263
|
+
)}
|
|
264
|
+
{onBookNowClick && (
|
|
265
|
+
<Button
|
|
266
|
+
variant="secondary"
|
|
267
|
+
size="sm"
|
|
268
|
+
onClick={onBookNowClick}
|
|
269
|
+
className="docket__footer-button docket__footer-button--primary">
|
|
270
|
+
Book now
|
|
271
|
+
</Button>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Docket is a container component for displaying quotation/docket information.
|
|
280
|
+
* It provides a structured layout for travel booking summaries with sections
|
|
281
|
+
* for accommodation, services, pricing, and actions.
|
|
282
|
+
*/
|
|
283
|
+
export const Docket: React.FC<DocketProps> = ({
|
|
284
|
+
dockets,
|
|
285
|
+
title = 'Your quotation',
|
|
286
|
+
onMoreOptionsClick,
|
|
287
|
+
onAddNewQuoteClick,
|
|
288
|
+
onViewClick,
|
|
289
|
+
onSaveClick,
|
|
290
|
+
onBookNowClick,
|
|
291
|
+
mode = 'multiple',
|
|
292
|
+
showHeader = true,
|
|
293
|
+
showFooter = true,
|
|
294
|
+
className = '',
|
|
295
|
+
onClick,
|
|
296
|
+
'data-testid': testId,
|
|
297
|
+
}) => {
|
|
298
|
+
const [isOpen, setIsOpen] = useState<boolean>(false);
|
|
299
|
+
const [isMobile, setIsMobile] = useState<boolean>(false);
|
|
300
|
+
|
|
301
|
+
useEffect(() => {
|
|
302
|
+
const checkMobile = () => {
|
|
303
|
+
setIsMobile(window.innerWidth < 1024); // Tablet and mobile breakpoint
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
checkMobile();
|
|
307
|
+
window.addEventListener('resize', checkMobile);
|
|
308
|
+
return () => window.removeEventListener('resize', checkMobile);
|
|
309
|
+
}, []);
|
|
310
|
+
|
|
311
|
+
const handleToggle = () => {
|
|
312
|
+
setIsOpen(!isOpen);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const handleClose = (e: React.MouseEvent) => {
|
|
316
|
+
e.stopPropagation();
|
|
317
|
+
setIsOpen(false);
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
const baseClass = 'docket';
|
|
321
|
+
const classes = [
|
|
322
|
+
baseClass,
|
|
323
|
+
isMobile && !isOpen ? 'docket--collapsed' : '',
|
|
324
|
+
isMobile && isOpen ? 'docket--open' : '',
|
|
325
|
+
className,
|
|
326
|
+
]
|
|
327
|
+
.filter(Boolean)
|
|
328
|
+
.join(' ');
|
|
329
|
+
|
|
330
|
+
const renderService = (service: ServiceDocket, index: number) => {
|
|
331
|
+
switch (service.type) {
|
|
332
|
+
case 'accommodation':
|
|
333
|
+
return (
|
|
334
|
+
<AccomodationDocket
|
|
335
|
+
key={`service-${index}`}
|
|
336
|
+
data={service as AccomodationDocketType}
|
|
337
|
+
/>
|
|
338
|
+
);
|
|
339
|
+
case 'transfer':
|
|
340
|
+
return (
|
|
341
|
+
<TransferDocket key={`service-${index}`} data={service as TransferDocketType} />
|
|
342
|
+
);
|
|
343
|
+
case 'excursion':
|
|
344
|
+
return (
|
|
345
|
+
<ExcursionDocket
|
|
346
|
+
key={`service-${index}`}
|
|
347
|
+
data={service as ExcursionDocketType}
|
|
348
|
+
/>
|
|
349
|
+
);
|
|
350
|
+
case 'otherService':
|
|
351
|
+
return (
|
|
352
|
+
<OtherServiceDocket
|
|
353
|
+
key={`service-${index}`}
|
|
354
|
+
data={service as OtherServiceDocketType}
|
|
355
|
+
/>
|
|
356
|
+
);
|
|
357
|
+
default:
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const renderContent = () => {
|
|
363
|
+
if (!dockets || dockets.length === 0) {
|
|
364
|
+
return <DocketEmptyState />;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Collect all services from all dockets
|
|
368
|
+
const allServices = dockets.flatMap((docket) => docket.services || []);
|
|
369
|
+
|
|
370
|
+
if (allServices.length === 0) {
|
|
371
|
+
return <DocketEmptyState />;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const renderedServices = allServices
|
|
375
|
+
.map((service, index) => renderService(service, index))
|
|
376
|
+
.filter(Boolean);
|
|
377
|
+
|
|
378
|
+
if (renderedServices.length === 0) {
|
|
379
|
+
return <DocketEmptyState />;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return <div className="docket__services">{renderedServices}</div>;
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Collect all prices from all dockets
|
|
386
|
+
const allPrices = dockets ? dockets.flatMap((docket) => docket.prices || []) : [];
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<>
|
|
390
|
+
{isMobile && !isOpen && (
|
|
391
|
+
<DocketCollapsedHeader title={title} onClick={handleToggle} />
|
|
392
|
+
)}
|
|
393
|
+
{isMobile && isOpen && (
|
|
394
|
+
<div className="docket__mobile-wrapper">
|
|
395
|
+
<div className="docket__close-header">
|
|
396
|
+
<div className="docket__close-header-content">
|
|
397
|
+
<h2 className="docket__close-header-text">Close</h2>
|
|
398
|
+
<button
|
|
399
|
+
className="docket__close-button"
|
|
400
|
+
onClick={handleClose}
|
|
401
|
+
aria-label="Close docket">
|
|
402
|
+
<Icon name="close" size="lg" />
|
|
403
|
+
</button>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
<div className={classes} onClick={onClick} data-testid={testId}>
|
|
407
|
+
{showHeader && (
|
|
408
|
+
<DocketHeader title={title} onMoreOptionsClick={onMoreOptionsClick} />
|
|
409
|
+
)}
|
|
410
|
+
<div className="docket__content">{renderContent()}</div>
|
|
411
|
+
{allPrices.length > 0 && (
|
|
412
|
+
<div className="docket__prices-section">
|
|
413
|
+
<DocketPrices prices={allPrices} />
|
|
414
|
+
</div>
|
|
415
|
+
)}
|
|
416
|
+
{showFooter && (
|
|
417
|
+
<DocketFooter
|
|
418
|
+
mode={mode}
|
|
419
|
+
onAddNewQuoteClick={onAddNewQuoteClick}
|
|
420
|
+
onViewClick={onViewClick}
|
|
421
|
+
onSaveClick={onSaveClick}
|
|
422
|
+
onBookNowClick={onBookNowClick}
|
|
423
|
+
/>
|
|
424
|
+
)}
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
{!isMobile && (
|
|
429
|
+
<div className={classes} onClick={onClick} data-testid={testId}>
|
|
430
|
+
{showHeader && (
|
|
431
|
+
<DocketHeader title={title} onMoreOptionsClick={onMoreOptionsClick} />
|
|
432
|
+
)}
|
|
433
|
+
<div className="docket__content">{renderContent()}</div>
|
|
434
|
+
{allPrices.length > 0 && (
|
|
435
|
+
<div className="docket__prices-section">
|
|
436
|
+
<Icon name="line" size="lg" />
|
|
437
|
+
<DocketPrices prices={allPrices} />
|
|
438
|
+
<Icon name="line" size="lg" />
|
|
439
|
+
</div>
|
|
440
|
+
)}
|
|
441
|
+
{showFooter && (
|
|
442
|
+
<DocketFooter
|
|
443
|
+
mode={mode}
|
|
444
|
+
onAddNewQuoteClick={onAddNewQuoteClick}
|
|
445
|
+
onViewClick={onViewClick}
|
|
446
|
+
onSaveClick={onSaveClick}
|
|
447
|
+
onBookNowClick={onBookNowClick}
|
|
448
|
+
/>
|
|
449
|
+
)}
|
|
450
|
+
</div>
|
|
451
|
+
)}
|
|
452
|
+
</>
|
|
453
|
+
);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
export default Docket;
|