mautourco-components 0.2.142 → 0.2.144

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 (24) hide show
  1. package/dist/components/molecules/LanguageSelector/LanguageSelector.css +120 -0
  2. package/dist/components/molecules/LanguageSelector/LanguageSelector.d.ts +11 -0
  3. package/dist/components/molecules/LanguageSelector/LanguageSelector.js +28 -0
  4. package/dist/components/molecules/TextWithIcon/TextWithIcon.js +1 -1
  5. package/dist/components/organisms/Docket/Docket.d.ts +31 -6
  6. package/dist/components/organisms/Docket/Docket.js +33 -13
  7. package/dist/components/organisms/Docket/DocketEmptyState.d.ts +14 -3
  8. package/dist/components/organisms/Docket/DocketEmptyState.js +7 -2
  9. package/dist/components/organisms/Docket/DocketFooter.d.ts +24 -0
  10. package/dist/components/organisms/Docket/DocketFooter.js +11 -3
  11. package/dist/components/organisms/DocketAccordion/DocketAccordion.d.ts +10 -1
  12. package/dist/components/organisms/DocketAccordion/DocketAccordion.js +2 -2
  13. package/dist/hooks/useMobile.d.ts +2 -1
  14. package/dist/hooks/useMobile.js +1 -0
  15. package/dist/styles/components/organism/docket.css +14 -20
  16. package/package.json +1 -1
  17. package/src/components/molecules/LanguageSelector/LanguageSelector.css +83 -0
  18. package/src/components/molecules/LanguageSelector/LanguageSelector.tsx +102 -0
  19. package/src/components/molecules/TextWithIcon/TextWithIcon.tsx +1 -1
  20. package/src/components/organisms/Docket/Docket.tsx +106 -24
  21. package/src/components/organisms/Docket/DocketEmptyState.tsx +29 -7
  22. package/src/components/organisms/Docket/DocketFooter.tsx +82 -26
  23. package/src/components/organisms/DocketAccordion/DocketAccordion.tsx +12 -2
  24. package/src/styles/components/organism/docket.css +13 -20
@@ -0,0 +1,83 @@
1
+ /* Language Selector - Trigger */
2
+ .language-selector__trigger {
3
+ @apply flex items-center justify-center gap-2 cursor-pointer;
4
+ @apply px-4;
5
+ height: 44px;
6
+ border: solid 1px var(--color-tuna-500);
7
+ border-radius: 12px;
8
+ transition: all 0.5s ease;
9
+ &:hover {
10
+ border-color: #262626;
11
+ }
12
+ &.language-selector__trigger--with-text {
13
+ border-color: #262626;
14
+ color: #262626;
15
+ }
16
+ }
17
+
18
+ .language-selector__placeholder {
19
+ color: var(--color-tuna-500);
20
+ }
21
+
22
+ .language-selector__trigger-icon {
23
+ @apply flex-shrink-0 text-[var(--color-neutral-800)];
24
+ }
25
+
26
+ .language-selector__trigger-text {
27
+ @apply text-sm font-medium text-[var(--color-neutral-800)];
28
+ }
29
+
30
+ .language-selector__trigger-chevron {
31
+ @apply flex-shrink-0 text-[var(--color-neutral-800)] transition-transform duration-200;
32
+ }
33
+
34
+ .language-selector__trigger[data-state='open'] .language-selector__trigger-chevron {
35
+ transform: rotate(180deg);
36
+ }
37
+
38
+ /* Popover */
39
+ .language-selector__popover {
40
+ @apply bg-white;
41
+ padding: var(--spacing-padding-px-2);
42
+ border-radius: var(--border-radius-rounded-xl);
43
+ min-width: 8rem;
44
+ }
45
+
46
+ /* Popover Content */
47
+ .language-selector__content {
48
+ @apply flex flex-col py-1 bg-white;
49
+ min-width: 8rem;
50
+ }
51
+
52
+ .language-selector__option {
53
+ @apply flex items-center w-full cursor-pointer gap-x-2;
54
+ height: 2.25rem;
55
+ padding-inline: var(--spacing-padding-px-3);
56
+ border-radius: var(--border-radius-rounded-md);
57
+ font-size: var(--typography-font-size-sm);
58
+ font-weight: var(--typography-font-weight-medium);
59
+ color: var(--color-neutral-800);
60
+ transition: background-color 0.2s ease;
61
+ }
62
+
63
+ .language-selector__option:hover {
64
+ background-color: var(--color-neutral-100);
65
+ }
66
+
67
+ .language-selector__option--selected {
68
+ background-color: var(--color-neutral-100);
69
+ }
70
+
71
+ .language-selector__selected-language {
72
+ @apply flex gap-x-2 items-center;
73
+ }
74
+
75
+ .language-selector__flag {
76
+ @apply flex items-center justify-center;
77
+ @apply grow-0 shrink-0 basis-4 h-4;
78
+ @apply rounded-full overflow-hidden;
79
+ .fi {
80
+ @apply w-4;
81
+ flex: 0 0 1.333333em;
82
+ }
83
+ }
@@ -0,0 +1,102 @@
1
+ import { cn } from '@/src/lib/utils';
2
+ import 'flag-icons/css/flag-icons.min.css';
3
+ import { useEffect, useState } from 'react';
4
+ import Icon from '../../atoms/Icon/Icon';
5
+ import { Text } from '../../atoms/Typography/Typography';
6
+ import { Popover, PopoverContent, PopoverTrigger } from '../../ui/popover';
7
+ import { languagesMap } from '../ServiceLanguages/constant';
8
+ import './LanguageSelector.css';
9
+
10
+ export const defaultLanguages = ['English', 'Français', 'German', 'Italian'];
11
+
12
+ export interface LanguageSelectorProps {
13
+ languages?: string[];
14
+ defaultLanguage?: string;
15
+ placeholder?: string;
16
+ onSelectLanguage?: (language: string) => void;
17
+ label?: string;
18
+ }
19
+
20
+ export default function LanguageSelector(props: LanguageSelectorProps) {
21
+ const {
22
+ languages = defaultLanguages,
23
+ defaultLanguage,
24
+ placeholder = 'Prefered language',
25
+ label,
26
+ onSelectLanguage,
27
+ } = props;
28
+
29
+ const [open, setOpen] = useState(false);
30
+ const [selectedLanguage, setSelectedLanguage] = useState<string | undefined>(
31
+ defaultLanguage
32
+ );
33
+
34
+ const handleSelectLanguage = (language: string) => {
35
+ setSelectedLanguage(language);
36
+ onSelectLanguage?.(language);
37
+ setOpen(false);
38
+ };
39
+
40
+ useEffect(() => {
41
+ setSelectedLanguage(defaultLanguage);
42
+ }, [defaultLanguage]);
43
+
44
+ return (
45
+ <div>
46
+ {label && (
47
+ <label className="mb-2">
48
+ <Text size="sm" as="span">
49
+ {label}
50
+ </Text>
51
+ </label>
52
+ )}
53
+ <Popover open={open} onOpenChange={setOpen}>
54
+ <PopoverTrigger asChild>
55
+ <button
56
+ type="button"
57
+ className={cn('language-selector__trigger', {
58
+ 'language-selector__trigger--with-text': !!selectedLanguage,
59
+ })}>
60
+ {selectedLanguage ? (
61
+ <span className="language-selector__selected-language">
62
+ <span className="language-selector__flag">
63
+ <i
64
+ className={`fi fi-${languagesMap[selectedLanguage.toLowerCase()]}`}></i>
65
+ </span>
66
+ {selectedLanguage}
67
+ </span>
68
+ ) : (
69
+ <span className="language-selector__placeholder">{placeholder}</span>
70
+ )}
71
+ <Icon
72
+ name="chevron-down"
73
+ size="sm"
74
+ className="language-selector__trigger-chevron"
75
+ />
76
+ </button>
77
+ </PopoverTrigger>
78
+ <PopoverContent
79
+ className="language-selector__popover"
80
+ align="end"
81
+ side="bottom"
82
+ sideOffset={8}>
83
+ <div className="language-selector__content">
84
+ {languages.map((language) => (
85
+ <div
86
+ className={cn('language-selector__option', {
87
+ 'language-selector__option--selected': selectedLanguage === language,
88
+ })}
89
+ onClick={() => handleSelectLanguage(language)}
90
+ key={language}>
91
+ <span className="language-selector__flag">
92
+ <i className={`fi fi-${languagesMap[language.toLowerCase()]}`}></i>
93
+ </span>
94
+ {language}
95
+ </div>
96
+ ))}
97
+ </div>
98
+ </PopoverContent>
99
+ </Popover>
100
+ </div>
101
+ );
102
+ }
@@ -56,7 +56,7 @@ function TextWithIcon(props: TextWithIconProps) {
56
56
  return (
57
57
  <div
58
58
  className={cn(
59
- 'flex gap-x-2',
59
+ 'flex gap-x-2 items-center',
60
60
  color === 'yellow' && 'text-[var(--color-yellow-600)]',
61
61
  color === 'accent' && 'text-[var(--color-text-accent)]'
62
62
  )}>
@@ -1,5 +1,6 @@
1
1
  import { ProposalSorted } from '@/src/types/docket/docket.types';
2
2
  import React, { useEffect, useState } from 'react';
3
+ import { Breakpoint } from '../../../hooks/useMobile';
3
4
  import '../../../styles/components/organism/docket.css';
4
5
  import {
5
6
  AccommodationService,
@@ -18,19 +19,30 @@ import { TransferDocket } from '../../molecules/TransferDocket/TransferDocket';
18
19
  import DocketAccordion from '../DocketAccordion/DocketAccordion';
19
20
  import { DocketCollapsedHeader } from './DocketCollapsedHeader';
20
21
  import { DocketEmptyState } from './DocketEmptyState';
21
- import { DocketFooter } from './DocketFooter';
22
+ import { DocketFooter, DocketFooterLabelsType } from './DocketFooter';
22
23
  import { DocketHeader } from './DocketHeader';
23
24
 
24
25
  // Re-export sub-components and their props
25
26
  export { DocketCollapsedHeader } from './DocketCollapsedHeader';
26
27
  export type { DocketCollapsedHeaderProps } from './DocketCollapsedHeader';
27
28
  export { DocketEmptyState } from './DocketEmptyState';
28
- export type { DocketEmptyStateProps } from './DocketEmptyState';
29
+ export type {
30
+ DocketEmptyStateLabelsType,
31
+ DocketEmptyStateProps,
32
+ } from './DocketEmptyState';
29
33
  export { DocketFooter } from './DocketFooter';
30
34
  export type { DocketFooterProps } from './DocketFooter';
31
35
  export { DocketHeader } from './DocketHeader';
32
36
  export type { DocketHeaderProps } from './DocketHeader';
33
37
 
38
+ export type DocketLabelsType = {
39
+ manageQuotation: string;
40
+ close: string;
41
+ /** Bold part of the empty state message */
42
+ emptyStateBold?: string;
43
+ /** Regular part of the empty state message */
44
+ emptyStateRegular?: string;
45
+ } & DocketFooterLabelsType;
34
46
  export interface DocketProps {
35
47
  /**
36
48
  * Array of nested Docket objects
@@ -128,11 +140,6 @@ export interface DocketProps {
128
140
  */
129
141
  onClick?: (e: React.MouseEvent<HTMLDivElement>) => void;
130
142
 
131
- /**
132
- * Optional data attributes for testing or tracking
133
- */
134
- 'data-testid'?: string;
135
-
136
143
  /**
137
144
  * Remove mode - replaces price chips with remove buttons in all service dockets
138
145
  */
@@ -143,6 +150,31 @@ export interface DocketProps {
143
150
  * Receives the service index and service data
144
151
  */
145
152
  onServiceRemove?: (index: number, service: DocketService) => void;
153
+
154
+ /**
155
+ * Button done mode - replaces the "Book now" button with a "Done" button
156
+ */
157
+ doneButtonMode?: boolean;
158
+
159
+ /**
160
+ * Handler for the "Done" button click
161
+ */
162
+ onDoneClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
163
+
164
+ /**
165
+ * Labels for the docket
166
+ */
167
+ labels?: DocketLabelsType;
168
+
169
+ /**
170
+ * Maximum number of proposals
171
+ */
172
+ maxProposals?: number;
173
+
174
+ /**
175
+ * URL of the empty state illustration image (when there are no proposals or no services)
176
+ */
177
+ emptyStateImageUrl?: string;
146
178
  }
147
179
 
148
180
  /**
@@ -150,7 +182,7 @@ export interface DocketProps {
150
182
  * It provides a structured layout for travel booking summaries with sections
151
183
  * for accommodation, services, pricing, and actions.
152
184
  */
153
- export const Docket: React.FC<DocketProps> = ({
185
+ export function Docket({
154
186
  proposals,
155
187
  activeProposal: initialActiveProposal = 0,
156
188
  onProposalSelect,
@@ -166,10 +198,25 @@ export const Docket: React.FC<DocketProps> = ({
166
198
  className = '',
167
199
  containerClassName = '',
168
200
  onClick,
169
- 'data-testid': testId,
170
201
  removeMode = false,
171
202
  onServiceRemove,
172
- }) => {
203
+ doneButtonMode = false,
204
+ onDoneClick,
205
+ maxProposals = 5,
206
+ emptyStateImageUrl,
207
+ labels = {
208
+ manageQuotation: 'Manage quotation',
209
+ close: 'Close',
210
+ addNewQuote: 'Add new quote',
211
+ compare: 'Compare',
212
+ view: 'View',
213
+ save: 'Save',
214
+ bookNow: 'Book now',
215
+ done: 'Done',
216
+ emptyStateBold: 'Start by adding a service here—',
217
+ emptyStateRegular: "you'll be able to create and compare multiple quotations.",
218
+ },
219
+ }: DocketProps): React.ReactElement {
173
220
  const [isOpen, setIsOpen] = useState<boolean>(false);
174
221
  const [isMobile, setIsMobile] = useState<boolean>(false);
175
222
  const [expandedProposalIndex, setExpandedProposalIndex] =
@@ -177,7 +224,7 @@ export const Docket: React.FC<DocketProps> = ({
177
224
 
178
225
  useEffect(() => {
179
226
  const checkMobile = () => {
180
- setIsMobile(window.innerWidth < 1024); // Tablet and mobile breakpoint
227
+ setIsMobile(window.innerWidth < Breakpoint.LG); // Desktop S breakpoint (1440px)
181
228
  };
182
229
 
183
230
  checkMobile();
@@ -260,7 +307,16 @@ export const Docket: React.FC<DocketProps> = ({
260
307
 
261
308
  const renderContent = () => {
262
309
  if (!proposals || proposals.length === 0) {
263
- return <DocketEmptyState />;
310
+ return (
311
+ <DocketEmptyState
312
+ imageUrl={emptyStateImageUrl}
313
+ labels={
314
+ labels?.emptyStateBold != null && labels?.emptyStateRegular != null
315
+ ? { bold: labels.emptyStateBold, regular: labels.emptyStateRegular }
316
+ : undefined
317
+ }
318
+ />
319
+ );
264
320
  }
265
321
 
266
322
  // If there are multiple proposals, render each in a DocketAccordion
@@ -276,6 +332,13 @@ export const Docket: React.FC<DocketProps> = ({
276
332
  expanded={index === expandedProposalIndex}
277
333
  onExpandedChange={(isExpanded) => handleProposalToggle(index, isExpanded)}
278
334
  moreOptions={accordionMoreOptions}
335
+ manageQuotationLabel={labels?.manageQuotation}
336
+ emptyStateImageUrl={emptyStateImageUrl}
337
+ emptyStateLabels={
338
+ labels?.emptyStateBold != null && labels?.emptyStateRegular != null
339
+ ? { bold: labels.emptyStateBold, regular: labels.emptyStateRegular }
340
+ : undefined
341
+ }
279
342
  />
280
343
  ))}
281
344
  </div>
@@ -287,7 +350,16 @@ export const Docket: React.FC<DocketProps> = ({
287
350
  const allServices = currentProposal.Services || [];
288
351
 
289
352
  if (allServices.length === 0) {
290
- return <DocketEmptyState />;
353
+ return (
354
+ <DocketEmptyState
355
+ imageUrl={emptyStateImageUrl}
356
+ labels={
357
+ labels?.emptyStateBold != null && labels?.emptyStateRegular != null
358
+ ? { bold: labels.emptyStateBold, regular: labels.emptyStateRegular }
359
+ : undefined
360
+ }
361
+ />
362
+ );
291
363
  }
292
364
 
293
365
  const renderedServices = allServices
@@ -295,7 +367,16 @@ export const Docket: React.FC<DocketProps> = ({
295
367
  .filter(Boolean);
296
368
 
297
369
  if (renderedServices.length === 0) {
298
- return <DocketEmptyState />;
370
+ return (
371
+ <DocketEmptyState
372
+ imageUrl={emptyStateImageUrl}
373
+ labels={
374
+ labels?.emptyStateBold != null && labels?.emptyStateRegular != null
375
+ ? { bold: labels.emptyStateBold, regular: labels.emptyStateRegular }
376
+ : undefined
377
+ }
378
+ />
379
+ );
299
380
  }
300
381
 
301
382
  return <div className="docket__services">{renderedServices}</div>;
@@ -305,7 +386,7 @@ export const Docket: React.FC<DocketProps> = ({
305
386
  const allPrices = proposals && proposals.length === 1 ? proposals[0].Prices || [] : [];
306
387
 
307
388
  const renderDocketContent = () => (
308
- <div className={classes} onClick={onClick} data-testid={testId}>
389
+ <div className={classes} onClick={onClick}>
309
390
  <DocketHeader
310
391
  title={title}
311
392
  moreOptions={moreOptions}
@@ -326,6 +407,10 @@ export const Docket: React.FC<DocketProps> = ({
326
407
  onViewClick={onViewClick}
327
408
  onSaveClick={onSaveClick}
328
409
  onBookNowClick={onBookNowClick}
410
+ doneButtonMode={doneButtonMode}
411
+ onDoneClick={onDoneClick}
412
+ labels={labels}
413
+ disableAddNewQuoteButton={proposals ? proposals.length >= maxProposals : false}
329
414
  />
330
415
  </div>
331
416
  );
@@ -341,23 +426,20 @@ export const Docket: React.FC<DocketProps> = ({
341
426
  )}
342
427
  {isMobile && isOpen && (
343
428
  <div className="docket__mobile-wrapper">
344
- <div className="docket__close-header">
429
+ <button className="docket__close-header" onClick={handleClose}>
345
430
  <div className="docket__close-header-content">
346
- <h2 className="docket__close-header-text">Close</h2>
347
- <button
348
- className="docket__close-button"
349
- onClick={handleClose}
350
- aria-label="Close docket">
431
+ <h2 className="docket__close-header-text">{labels?.close}</h2>
432
+ <div className="docket__close-button" aria-label="Close docket">
351
433
  <Icon name="close" size="lg" />
352
- </button>
434
+ </div>
353
435
  </div>
354
- </div>
436
+ </button>
355
437
  {renderDocketContent()}
356
438
  </div>
357
439
  )}
358
440
  {!isMobile && renderDocketContent()}
359
441
  </div>
360
442
  );
361
- };
443
+ }
362
444
 
363
445
  export default Docket;
@@ -1,6 +1,23 @@
1
1
  import React from 'react';
2
2
 
3
+ export type DocketEmptyStateLabelsType = {
4
+ /** Bold part of the empty state message */
5
+ bold: string;
6
+ /** Regular part of the empty state message */
7
+ regular: string;
8
+ };
9
+
3
10
  export interface DocketEmptyStateProps {
11
+ /**
12
+ * URL of the empty state illustration image
13
+ */
14
+ imageUrl?: string;
15
+
16
+ /**
17
+ * Labels for the empty state text (bold and regular parts)
18
+ */
19
+ labels?: DocketEmptyStateLabelsType;
20
+
4
21
  /**
5
22
  * Additional CSS classes
6
23
  */
@@ -10,7 +27,16 @@ export interface DocketEmptyStateProps {
10
27
  /**
11
28
  * Empty state component for docket when no content is provided
12
29
  */
30
+ const DEFAULT_EMPTY_STATE_IMAGE = '/images/docket-empty-illustration.svg';
31
+
32
+ const DEFAULT_EMPTY_STATE_LABELS: DocketEmptyStateLabelsType = {
33
+ bold: 'Start by adding a service here—',
34
+ regular: "you'll be able to create and compare multiple quotations.",
35
+ };
36
+
13
37
  export const DocketEmptyState: React.FC<DocketEmptyStateProps> = ({
38
+ imageUrl = DEFAULT_EMPTY_STATE_IMAGE,
39
+ labels = DEFAULT_EMPTY_STATE_LABELS,
14
40
  className = '',
15
41
  }) => {
16
42
  return (
@@ -18,18 +44,14 @@ export const DocketEmptyState: React.FC<DocketEmptyStateProps> = ({
18
44
  <div className="docket__empty-state-content">
19
45
  <div className="docket__empty-state-illustration">
20
46
  <img
21
- src="/images/docket-empty-illustration.svg"
47
+ src={imageUrl}
22
48
  alt="Empty docket illustration"
23
49
  className="docket__empty-state-image"
24
50
  />
25
51
  </div>
26
52
  <p className="docket__empty-state-text">
27
- <span className="docket__empty-state-text-bold">
28
- Start by adding a service here—
29
- </span>
30
- <span className="docket__empty-state-text-regular">
31
- you'll be able to create and compare multiple quotations.
32
- </span>
53
+ <span className="docket__empty-state-text-bold">{labels.bold}</span>
54
+ <span className="docket__empty-state-text-regular">{labels.regular}</span>
33
55
  </p>
34
56
  </div>
35
57
  </div>
@@ -1,6 +1,15 @@
1
1
  import React from 'react';
2
2
  import Button from '../../atoms/Button/Button';
3
3
 
4
+ export type DocketFooterLabelsType = {
5
+ addNewQuote: string;
6
+ compare: string;
7
+ view: string;
8
+ save: string;
9
+ bookNow: string;
10
+ done: string;
11
+ };
12
+
4
13
  export interface DocketFooterProps {
5
14
  /**
6
15
  * Handler for "Add new quote" button click
@@ -36,6 +45,26 @@ export interface DocketFooterProps {
36
45
  * Additional CSS classes
37
46
  */
38
47
  className?: string;
48
+
49
+ /**
50
+ * Button done mode - replaces the "Book now" button with a "Done" button
51
+ */
52
+ doneButtonMode?: boolean;
53
+
54
+ /**
55
+ * Handler for the "Done" button click
56
+ */
57
+ onDoneClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
58
+
59
+ /**
60
+ * Disable the "Add new quote" button
61
+ */
62
+ disableAddNewQuoteButton?: boolean;
63
+
64
+ /**
65
+ * Labels for the buttons
66
+ */
67
+ labels?: DocketFooterLabelsType;
39
68
  }
40
69
 
41
70
  /**
@@ -49,19 +78,34 @@ export const DocketFooter: React.FC<DocketFooterProps> = ({
49
78
  onSaveClick,
50
79
  onBookNowClick,
51
80
  className = '',
81
+ doneButtonMode = false,
82
+ onDoneClick,
83
+ disableAddNewQuoteButton = false,
84
+ labels = {
85
+ addNewQuote: 'Add new quote',
86
+ compare: 'Compare',
87
+ view: 'View',
88
+ save: 'Save',
89
+ bookNow: 'Book now',
90
+ done: 'Done',
91
+ },
52
92
  }) => {
93
+ const isDoneMode = doneButtonMode && onDoneClick;
94
+
53
95
  return (
54
96
  <div className={`docket__footer ${className}`}>
55
- {(onAddNewQuoteClick || onCompareClick) && (
97
+ {!isDoneMode && (onAddNewQuoteClick || onCompareClick) && (
56
98
  <div className="docket__footer-top-buttons">
57
99
  {onAddNewQuoteClick && (
58
100
  <Button
59
101
  variant="outline-secondary"
60
102
  size="sm"
61
- leadingIcon="plus"
103
+ leadingIcon={disableAddNewQuoteButton ? undefined : 'plus'}
104
+ trailingIcon={disableAddNewQuoteButton ? 'info' : undefined}
105
+ disabled={disableAddNewQuoteButton}
62
106
  onClick={onAddNewQuoteClick}
63
107
  className={`docket__footer-button ${!showCompareButton ? 'docket__footer-button--full' : ''}`}>
64
- Add new quote
108
+ {labels.addNewQuote}
65
109
  </Button>
66
110
  )}
67
111
  {showCompareButton && onCompareClick && (
@@ -71,38 +115,50 @@ export const DocketFooter: React.FC<DocketFooterProps> = ({
71
115
  onClick={onCompareClick}
72
116
  leadingIcon="document"
73
117
  className="docket__footer-button">
74
- Compare
118
+ {labels.compare}
75
119
  </Button>
76
120
  )}
77
121
  </div>
78
122
  )}
79
123
  <div className="docket__footer-actions">
80
- {onViewClick && (
81
- <Button
82
- variant="outline-secondary"
83
- size="sm"
84
- onClick={onViewClick}
85
- className="docket__footer-view-link">
86
- <span className="docket__footer-view-text">View</span>
87
- </Button>
88
- )}
89
- {onSaveClick && (
90
- <Button
91
- variant="outline-secondary"
92
- size="sm"
93
- onClick={onSaveClick}
94
- className="docket__footer-button">
95
- Save
96
- </Button>
97
- )}
98
- {onBookNowClick && (
124
+ {isDoneMode ? (
99
125
  <Button
100
126
  variant="secondary"
101
127
  size="sm"
102
- onClick={onBookNowClick}
103
- className="docket__footer-button docket__footer-button--primary">
104
- Book now
128
+ onClick={onDoneClick}
129
+ className="docket__footer-button docket__footer-button--primary docket__footer-button--half">
130
+ {labels.done}
105
131
  </Button>
132
+ ) : (
133
+ <>
134
+ {onViewClick && (
135
+ <Button
136
+ variant="outline-secondary"
137
+ size="sm"
138
+ onClick={onViewClick}
139
+ className="docket__footer-button">
140
+ <span className="docket__footer-view-text">{labels.view}</span>
141
+ </Button>
142
+ )}
143
+ {onSaveClick && (
144
+ <Button
145
+ variant="outline-secondary"
146
+ size="sm"
147
+ onClick={onSaveClick}
148
+ className="docket__footer-button">
149
+ {labels.save}
150
+ </Button>
151
+ )}
152
+ {onBookNowClick && (
153
+ <Button
154
+ variant="secondary"
155
+ size="sm"
156
+ onClick={onBookNowClick}
157
+ className="docket__footer-button docket__footer-button--primary">
158
+ {labels.bookNow}
159
+ </Button>
160
+ )}
161
+ </>
106
162
  )}
107
163
  </div>
108
164
  </div>
@@ -18,7 +18,7 @@ import DocketPrices from '../../molecules/DocketPrices';
18
18
  import { ExcursionDocket } from '../../molecules/ExcursionDocket/ExcursionDocket';
19
19
  import { OtherServiceDocket } from '../../molecules/OtherServiceDocket/OtherServiceDocket';
20
20
  import { TransferDocket } from '../../molecules/TransferDocket/TransferDocket';
21
- import { DocketEmptyState } from '../Docket/Docket';
21
+ import { DocketEmptyState, type DocketEmptyStateLabelsType } from '../Docket/Docket';
22
22
 
23
23
  export interface DocketAccordionProps {
24
24
  proposal: ProposalSorted;
@@ -29,6 +29,14 @@ export interface DocketAccordionProps {
29
29
  expanded?: boolean;
30
30
  onExpandedChange?: (expanded: boolean) => void;
31
31
  moreOptions?: ActionDropdownItem[];
32
+ /**
33
+ * URL of the empty state illustration image (when the proposal has no services)
34
+ */
35
+ emptyStateImageUrl?: string;
36
+ /**
37
+ * Labels for the empty state (bold and regular text) when the proposal has no services
38
+ */
39
+ emptyStateLabels?: DocketEmptyStateLabelsType;
32
40
  }
33
41
 
34
42
  /**
@@ -52,6 +60,8 @@ export default function DocketAccordion({
52
60
  onExpandedChange,
53
61
  moreOptions = [],
54
62
  manageQuotationLabel = 'Manage quotation',
63
+ emptyStateImageUrl,
64
+ emptyStateLabels,
55
65
  }: DocketAccordionProps) {
56
66
  const allServices = proposal.Services || [];
57
67
 
@@ -157,7 +167,7 @@ export default function DocketAccordion({
157
167
  footer={allServices?.length > 0 ? renderAccordionFooter : undefined}>
158
168
  <div className="docket-accordion__content">
159
169
  {allServices.length === 0 ? (
160
- <DocketEmptyState />
170
+ <DocketEmptyState imageUrl={emptyStateImageUrl} labels={emptyStateLabels} />
161
171
  ) : (
162
172
  <div className="docket-accordion__services">
163
173
  {allServices.map((service, index) => renderService(service, index))}