@rh-support/troubleshoot 2.3.0-alpha.0 → 2.4.5-beta.1

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 (58) hide show
  1. package/lib/esm/components/AccountInfo/OwnerSelector.d.ts.map +1 -1
  2. package/lib/esm/components/AccountInfo/OwnerSelector.js +1 -1
  3. package/lib/esm/components/CaseEditView/CaseDetailsTabs.js +2 -2
  4. package/lib/esm/components/CaseEditView/CaseOverview/CaseType.d.ts.map +1 -1
  5. package/lib/esm/components/CaseEditView/CaseOverview/CaseType.js +18 -4
  6. package/lib/esm/components/CaseEditView/CaseOverview/index.d.ts.map +1 -1
  7. package/lib/esm/components/CaseEditView/CaseOverview/index.js +2 -2
  8. package/lib/esm/components/CaseEditView/CaseSolutions/CaseSolutions.d.ts.map +1 -1
  9. package/lib/esm/components/CaseEditView/CaseSolutions/CaseSolutions.js +4 -2
  10. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseInternalStatus.d.ts.map +1 -1
  11. package/lib/esm/components/CaseEditView/Tabs/CaseDetails/CaseInternalStatus.js +2 -1
  12. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.d.ts.map +1 -1
  13. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.js +6 -4
  14. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CommentSearch.d.ts.map +1 -1
  15. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CommentSearch.js +3 -2
  16. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment.d.ts.map +1 -1
  17. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment.js +4 -1
  18. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.css +95 -21
  19. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.d.ts.map +1 -1
  20. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.js +269 -137
  21. package/lib/esm/components/CaseInformation/Fts.d.ts.map +1 -1
  22. package/lib/esm/components/CaseInformation/Fts.js +6 -5
  23. package/lib/esm/components/CaseManagement/Cep.d.ts.map +1 -1
  24. package/lib/esm/components/CaseManagement/Cep.js +3 -2
  25. package/lib/esm/components/CaseManagement/RHAssociatesSelector.d.ts.map +1 -1
  26. package/lib/esm/components/CaseManagement/RHAssociatesSelector.js +11 -5
  27. package/lib/esm/components/CaseManagement/SendNotifications/CaseContactSelector.d.ts.map +1 -1
  28. package/lib/esm/components/CaseManagement/SendNotifications/CaseContactSelector.js +11 -5
  29. package/lib/esm/components/Recommendations/AsideResults.d.ts.map +1 -1
  30. package/lib/esm/components/Recommendations/AsideResults.js +3 -1
  31. package/lib/esm/components/Recommendations/EARules/EARule.d.ts.map +1 -1
  32. package/lib/esm/components/Recommendations/EARules/EARule.js +0 -2
  33. package/lib/esm/components/Recommendations/EARules/EARuleInfoInline.d.ts.map +1 -1
  34. package/lib/esm/components/Recommendations/EARules/EARuleInfoInline.js +2 -2
  35. package/lib/esm/components/Recommendations/Recommendations.d.ts.map +1 -1
  36. package/lib/esm/components/Recommendations/Recommendations.js +19 -8
  37. package/lib/esm/components/SubmitCase/SubmitCase.js +3 -3
  38. package/lib/esm/components/Suggestions/TopContent.d.ts.map +1 -1
  39. package/lib/esm/components/Suggestions/TopContent.js +1 -4
  40. package/lib/esm/components/shared/fileUpload/fileSelectors/WidgetFileSelector.d.ts +2 -2
  41. package/lib/esm/components/shared/fileUpload/fileSelectors/WidgetFileSelector.d.ts.map +1 -1
  42. package/lib/esm/components/shared/fileUpload/fileSelectors/WidgetFileSelector.js +10 -1
  43. package/lib/esm/components/wizardLayout/WizardLayout.d.ts.map +1 -1
  44. package/lib/esm/components/wizardLayout/WizardLayout.js +4 -1
  45. package/lib/esm/components/wizardLayout/WizardMain.d.ts.map +1 -1
  46. package/lib/esm/components/wizardLayout/WizardMain.js +5 -0
  47. package/lib/esm/components/wizardLayout/WizardNavigation.d.ts.map +1 -1
  48. package/lib/esm/components/wizardLayout/WizardNavigation.js +35 -25
  49. package/lib/esm/hooks/useWizard.js +1 -1
  50. package/lib/esm/reducers/CaseReducer.d.ts +1 -1
  51. package/lib/esm/reducers/CaseReducer.d.ts.map +1 -1
  52. package/lib/esm/reducers/CaseReducer.js +13 -9
  53. package/lib/esm/reducers/SessionRestoreReducer.d.ts +1 -1
  54. package/lib/esm/reducers/SessionRestoreReducer.d.ts.map +1 -1
  55. package/lib/esm/reducers/SessionRestoreReducer.js +14 -121
  56. package/lib/esm/scss/_main.scss +2 -1
  57. package/lib/esm/scss/_pf-overrides.scss +6 -0
  58. package/package.json +11 -12
@@ -9,12 +9,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import './Timeline.css';
11
11
  import { caseHistory } from '@cee-eng/hydrajs';
12
- import { Avatar, Button, DatePicker, EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, MenuToggle, SearchInput, Select, SelectList, SelectOption, Spinner, } from '@patternfly/react-core';
12
+ import { Avatar, Button, DatePicker, EmptyState, EmptyStateBody, EmptyStateHeader, EmptyStateIcon, EmptyStateVariant, MenuToggle, Pagination, SearchInput, Select, SelectOption, Spinner, } from '@patternfly/react-core';
13
+ import ExclamationCircleIcon from '@patternfly/react-icons/dist/js/icons/exclamation-circle-icon';
13
14
  import SearchIcon from '@patternfly/react-icons/dist/js/icons/search-icon';
14
- import { PaginatedList } from '@rh-support/components';
15
+ import globalDangerColor100 from '@patternfly/react-tokens/dist/js/global_danger_color_100';
16
+ import { format, fromUnixTime, isSameDay, parse } from 'date-fns';
15
17
  import debounce from 'lodash/debounce';
16
18
  import isEmpty from 'lodash/isEmpty';
17
- import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
19
+ import React, { useEffect, useRef, useState } from 'react';
18
20
  import { Trans, useTranslation } from 'react-i18next';
19
21
  const TimelineEvent = ({ date, text, side, user, useAvatar }) => {
20
22
  if (side === 'left') {
@@ -29,7 +31,7 @@ const TimelineEvent = ({ date, text, side, user, useAvatar }) => {
29
31
  React.createElement("div", { className: `contentTimeline content-${side}-history content-${side}-${user}` },
30
32
  React.createElement("p", { className: `content-time-${side}-history` }, text))));
31
33
  }
32
- return (React.createElement("div", { className: `${side}-node ${user}`, role: "complementary", "aria-label": `${user}'s timeline event content ` },
34
+ return (React.createElement("div", { className: `${side}-node ${user}`, role: "complementary", "aria-label": `${user}'s timeline event content` },
33
35
  React.createElement("div", { className: `contentTimeline content-${side}-history content-${side}-${user}` },
34
36
  React.createElement("h2", { className: `content-date-${side}-history` }, date),
35
37
  text && React.createElement("p", { className: `content-time-${side}-history` }, text))));
@@ -37,14 +39,18 @@ const TimelineEvent = ({ date, text, side, user, useAvatar }) => {
37
39
  const TimelineNode = React.forwardRef(({ leftEvent, rightEvent, user, useAvatar }, ref) => {
38
40
  return (React.createElement("div", { className: "timeline-node", role: "region", "aria-label": "Timeline node" },
39
41
  leftEvent && React.createElement(TimelineEvent, Object.assign({}, leftEvent, { side: "left", useAvatar: useAvatar })),
40
- React.createElement("div", { ref: ref, "aria-label": `${useAvatar ? 'Open user profile' : 'Marker'}`, tabIndex: 0 }, useAvatar ? (React.createElement(Avatar, { src: 'https://www.patternfly.org/images/668560cd.svg', className: `timeline-avatar timeline-avatar-${user}`, alt: `${user}'s avatar` })) : (React.createElement("div", { className: `timeline-marker timeline-${user}` }))),
42
+ React.createElement("div", { ref: ref, "aria-label": `${useAvatar ? 'User profile picture' : 'Timeline marker'}`, tabIndex: -1 }, useAvatar ? (React.createElement(Avatar, { src: 'https://www.patternfly.org/images/668560cd.svg', className: `timeline-avatar timeline-avatar-${user}`, alt: `${user}'s avatar` })) : (React.createElement("div", { className: `timeline-marker timeline-${user}` }))),
41
43
  rightEvent && React.createElement(TimelineEvent, Object.assign({}, rightEvent, { side: "right" }))));
42
44
  });
43
- //Handling changes in case fields to be displayed to the user
45
+ /**
46
+ *
47
+ * @param {Object} item - The item object to process.
48
+ * @returns {string} The formatted text string.
49
+ */
44
50
  const applyReplacements = (item) => {
45
- if (!item || typeof item !== 'object') {
51
+ if (!item || typeof item !== 'object')
46
52
  return '';
47
- }
53
+ // API field names to easy to read display names
48
54
  const fieldNameMapping = {
49
55
  ContactId: 'Contact',
50
56
  RH_Product__c: 'Product',
@@ -55,94 +61,171 @@ const applyReplacements = (item) => {
55
61
  BusinessHoursId: 'Business Hours',
56
62
  FolderId__c: 'Case Group',
57
63
  Status: 'Status',
64
+ Customer_Escalation__c: 'Customer Escalation',
65
+ SlaStartDate: 'SLA Start Date',
66
+ Case_Language__c: 'Case Language',
67
+ Alternate_Id__c: 'Personal reference number',
68
+ SBR_Group__c: 'SBR Group',
58
69
  };
59
- let fieldName = fieldNameMapping[item.fieldName] || item.fieldLabel || item.fieldName || '';
60
- const getNameValue = (prefix) => {
61
- const keyMap = {
62
- Product: 'product',
63
- Version: 'version',
64
- Account: 'account',
65
- Contact: 'contact',
66
- Entitlement: 'entitlement',
67
- 'Business Hours': 'businessHours',
68
- Owner: 'queueNameOwner',
69
- };
70
- const baseKey = keyMap[fieldName] || fieldName.replace(/\s/g, '');
71
- const key = `${baseKey}${prefix}Name`;
72
- return item[key] || item[`${prefix}Value`] || '';
70
+ // https://help.salesforce.com/s/articleView?id=000384484&type=1
71
+ // adding in option for multiselect fields provided by salesforce.
72
+ // in future for more multiselect fields just add to below array.
73
+ const multiSelectFields = ['SBR_Group__c'];
74
+ // language codes to language names
75
+ const languageCodeMapping = {
76
+ de: 'German',
77
+ en: 'English',
78
+ es: 'Spanish',
79
+ fr: 'French',
80
+ it: 'Italian',
81
+ pt: 'Portuguese',
82
+ ko: 'Korean',
83
+ jp: 'Japanese',
84
+ zh: 'Chinese',
73
85
  };
86
+ // field display names to prefix keys used in item properties
87
+ const keyMap = {
88
+ Product: 'product',
89
+ Version: 'version',
90
+ Account: 'account',
91
+ Contact: 'contact',
92
+ Entitlement: 'entitlement',
93
+ 'Business Hours': 'businessHours',
94
+ Owner: 'queueNameOwner',
95
+ 'Customer Escalation': 'customerEscalation',
96
+ 'Case Group': 'caseGroup',
97
+ 'SLA Start Date': 'slaStartDate',
98
+ 'Case Language': 'caseLanguage',
99
+ 'Personal reference number': 'alternateId',
100
+ };
101
+ // get the mapped display name for a field
102
+ const getMappedFieldName = (fieldName) => fieldNameMapping[fieldName] || item.fieldLabel || fieldName || '';
103
+ // get the new or old value using the prefix
104
+ const getNameValue = (prefix) => item[`${keyMap[getMappedFieldName(item.fieldName)]}${prefix}Name`] ||
105
+ item[`${prefix}Value`] ||
106
+ item[`${prefix.toLowerCase()}Value`] ||
107
+ '';
108
+ // formatting functions
109
+ const formatLanguageChange = (fieldName, oldLanguage, newLanguage) => `changed ${fieldName} from ${oldLanguage} to ${newLanguage}`;
110
+ const formatSimpleChange = (fieldName, value) => `set ${fieldName} to ${value}`;
111
+ const formatBooleanChange = (fieldName, value) => `set ${fieldName} to ${value}`;
112
+ // get field name and values
113
+ const fieldName = getMappedFieldName(item.fieldName);
74
114
  const newValue = getNameValue('New');
75
115
  const oldValue = getNameValue('Old');
76
116
  let newText = '';
77
- if (item.fieldName === 'OwnerId' && item.createdByUserName) {
78
- newText = `set ${fieldName} to ${item.createdByUserName}`;
79
- }
80
- else if (item.fieldName === 'Status') {
81
- if (item.oldValue && item.newValue) {
82
- newText = `changed ${fieldName} from ${item.oldValue} to ${item.newValue}`;
117
+ // special field handling below, useful for future exposed case history fields etc / api changes
118
+ switch (item.fieldName) {
119
+ case 'Case_Language__c': {
120
+ const newLanguage = languageCodeMapping[item.newValue] || item.newValue;
121
+ const oldLanguage = languageCodeMapping[item.oldValue] || item.oldValue;
122
+ newText =
123
+ item.oldValue && item.newValue
124
+ ? formatLanguageChange(fieldName, oldLanguage, newLanguage)
125
+ : formatSimpleChange(fieldName, newLanguage || 'empty');
126
+ break;
83
127
  }
84
- else if (item.newValue) {
85
- newText = `set ${fieldName} to ${item.newValue}`;
86
- }
87
- }
88
- else if (oldValue && newValue) {
89
- newText = `changed ${fieldName} from ${oldValue} to ${newValue}`;
90
- }
91
- else if (newValue) {
92
- newText = `set ${fieldName} to ${newValue}`;
93
- }
94
- else if (item.newValue !== undefined) {
95
- newText = `set ${fieldName} to ${item.newValue}`;
96
- }
97
- // Handle boolean values
98
- if (typeof item.newValue === 'boolean') {
99
- newText = `set ${fieldName} to ${item.newValue}`;
100
- }
101
- // Handle date values
102
- if (item.fieldName === 'SlaStartDate' && item.newValue) {
103
- newText = `set ${fieldName} to ${item.newValue}`;
128
+ case 'OwnerId':
129
+ if (item.createdByUserName)
130
+ newText = formatSimpleChange(fieldName, item.createdByUserName);
131
+ break;
132
+ case 'Status':
133
+ newText =
134
+ item.oldValue && item.newValue
135
+ ? `changed ${fieldName} from ${item.oldValue} to ${item.newValue}`
136
+ : formatSimpleChange(fieldName, item.newValue || 'empty');
137
+ break;
138
+ case 'Customer_Escalation__c':
139
+ if (item.createdByUserName)
140
+ newText = 'started Escalation for this ticket';
141
+ break;
142
+ case 'SlaStartDate':
143
+ if (item.newValue)
144
+ newText = formatSimpleChange(fieldName, item.newValue);
145
+ break;
146
+ case 'RemoteSessionTermsAckedBy__c':
147
+ newText = 'Acked Remote Rider terms';
148
+ break;
149
+ default:
150
+ if (oldValue && newValue) {
151
+ if (multiSelectFields.includes(item.fieldName)) {
152
+ // in salesforce multi-select picklist values are stored as a single string, with each value separated by a semicolon
153
+ const oldValues = oldValue.split(';').map((s) => s.trim());
154
+ const newValues = newValue.split(';').map((s) => s.trim());
155
+ const added = newValues.filter((nv) => !oldValues.includes(nv));
156
+ const removed = oldValues.filter((ov) => !newValues.includes(ov));
157
+ if (added.length > 0 && removed.length === 0) {
158
+ newText = `added ${added.join(', ')} to ${fieldName}`;
159
+ }
160
+ else if (removed.length > 0 && added.length === 0) {
161
+ newText = `removed ${removed.join(', ')} from ${fieldName}`;
162
+ }
163
+ else if (added.length > 0 && removed.length > 0) {
164
+ newText = `changed ${fieldName}, added ${added.join(', ')} and removed ${removed.join(', ')}`;
165
+ }
166
+ else {
167
+ newText = `changed ${fieldName} from ${oldValue} to ${newValue}`;
168
+ }
169
+ }
170
+ else {
171
+ newText = `changed ${fieldName} from ${oldValue} to ${newValue}`;
172
+ }
173
+ }
174
+ else if (newValue) {
175
+ newText = formatSimpleChange(fieldName, newValue);
176
+ }
177
+ else if (oldValue && (newValue === undefined || newValue === '')) {
178
+ newText = `removed ${fieldName}, was ${oldValue}`;
179
+ }
180
+ else if (item.newValue !== undefined) {
181
+ newText = formatSimpleChange(fieldName, item.newValue);
182
+ }
183
+ break;
104
184
  }
105
- // Replace 'Problem Statement' with 'Title'
106
- if (typeof newText === 'string') {
185
+ if (typeof item.newValue === 'boolean')
186
+ newText = formatBooleanChange(fieldName, item.newValue);
187
+ if (typeof newText === 'string')
107
188
  newText = newText.replace(/Problem Statement/g, 'Title');
108
- }
109
189
  return newText;
110
190
  };
111
191
  // function to change the apiResponse
112
- const specialTimelineData = (apiResponse) => {
113
- return Object.assign(Object.assign({}, apiResponse), { historyItems: apiResponse.historyItems.map((item) => (Object.assign(Object.assign({}, item), { outputText: item.type === 'Change' ? applyReplacements(item) : item.outputText }))) });
114
- };
115
192
  const transformApiResponseToTimelineData = (apiResponse) => {
116
193
  let previousUser = null;
117
- const modifiedApiResponse = specialTimelineData(apiResponse);
118
- const timelineEvents = modifiedApiResponse.historyItems.map((item, index) => {
194
+ const timelineEvents = apiResponse.historyItems
195
+ // Filter out 'Case Group' changes because we don't have decode of value
196
+ .filter((item) => item.fieldName !== 'FolderId__c')
197
+ .map((item, index) => {
119
198
  const currentUser = item.createdByUserName;
120
- // If the current user is the same as the previous one, and it's not the first item, don't use an avatar. use !! cause previousUser start null.
121
199
  const useAvatar = index === 0 || (!!previousUser && currentUser !== previousUser);
122
- // Update the previousUser for the next iteration
123
200
  previousUser = currentUser;
124
- const eventDate = new Date(item.createdDate);
125
- const date = eventDate.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
126
- const time = eventDate.toLocaleTimeString('en-US', {
127
- hour: 'numeric',
128
- minute: '2-digit',
129
- hour12: true,
130
- timeZoneName: 'short',
131
- });
201
+ let eventDate;
202
+ if (typeof item.createdDate === 'string' && item.createdDate.includes('T')) {
203
+ eventDate = parse(item.createdDate, "yyyy-MM-dd'T'HH:mm:ss'Z'", new Date());
204
+ }
205
+ else {
206
+ eventDate = fromUnixTime(item.createdDate / 1000);
207
+ }
132
208
  const user = item.createdByCustomer !== undefined
133
- ? item.createdByCustomer //using new case history field to determine this
209
+ ? item.createdByCustomer
134
210
  ? 'customer'
135
211
  : 'internal'
136
- : currentUser === 'Case Diagnostics' || currentUser === 'GSS Tools' //for now since it is not on prod yet we have this same fallback
212
+ : currentUser === 'Case Diagnostics' || currentUser === 'GSS Tools'
137
213
  ? 'internal'
138
214
  : 'customer';
139
215
  const leftEvent = {
140
- date: date,
141
- text: time,
216
+ date: format(eventDate, 'MMMM d, yyyy'),
217
+ text: format(eventDate, 'h:mm a zzz'),
142
218
  };
219
+ let outputText = item.outputText;
220
+ if (item.type === 'Change') {
221
+ outputText = applyReplacements(item);
222
+ }
223
+ else {
224
+ outputText = stripHTML(outputText);
225
+ }
143
226
  const rightEvent = {
144
- date: `${item.createdByUserName} ${stripHTML(item.outputText)}`, //will change name later from date, for now date is title and text stays text due to initial set up mimicking same ITimelineEvent
145
- text: '', //future iterations of API make this the 'show more' field
227
+ date: `${item.createdByUserName} ${outputText}`,
228
+ text: '',
146
229
  user: user,
147
230
  useAvatar: useAvatar,
148
231
  };
@@ -161,27 +244,45 @@ function stripHTML(htmlString) {
161
244
  const Timeline = ({ caseNumber }) => {
162
245
  const { t } = useTranslation();
163
246
  const [timelineEvents, setTimelineEvents] = useState([]);
247
+ const [originalTimelineEvents, setOriginalTimelineEvents] = useState([]);
164
248
  const [isFetchingData, setIsFetchingData] = useState();
165
- const [paginatedListLength, setPaginatedListLength] = useState();
166
249
  const lastNodeRef = useRef(null);
167
- const [dateValue, setDateValue] = React.useState('');
250
+ const [dateValue, setDateValue] = useState('');
168
251
  const [searchValue, setSearchValue] = useState('');
169
252
  const [filteredEvents, setFilteredEvents] = useState([]);
170
253
  const [isSelectOpen, setIsSelectOpen] = useState(false);
171
254
  const [selectedOrder, setSelectedOrder] = useState('Newest to Oldest');
172
- const handleSelectToggle = () => {
255
+ const [fetchError, setFetchError] = useState(null);
256
+ const [page, setPage] = useState(1);
257
+ const [perPage, setPerPage] = useState(20);
258
+ const onSetPage = (_, newPage) => {
259
+ setPage(newPage);
260
+ };
261
+ const onPerPageSelect = (_, newPerPage) => {
262
+ setPerPage(newPerPage);
263
+ setPage(1);
264
+ };
265
+ const renderPagination = () => (React.createElement(Pagination, { itemCount: filteredEvents.length, perPage: perPage, page: page, onSetPage: onSetPage, onPerPageSelect: onPerPageSelect, perPageOptions: [
266
+ { title: '5', value: 5 },
267
+ { title: '10', value: 10 },
268
+ { title: '15', value: 15 },
269
+ { title: '20', value: 20 },
270
+ ], isCompact: true, variant: "top" }));
271
+ const handleSelectToggle = (e) => {
272
+ e.preventDefault();
173
273
  setIsSelectOpen(!isSelectOpen);
174
274
  };
175
- const handleSelect = (_event, value) => {
176
- setSelectedOrder(value);
275
+ const handleSelect = (e, value) => {
276
+ e.preventDefault();
277
+ if (value !== selectedOrder) {
278
+ setSelectedOrder(value);
279
+ reorderTimelineEvents(value);
280
+ }
177
281
  setIsSelectOpen(false);
178
- reorderTimelineEvents(value);
179
282
  };
180
283
  const reorderTimelineEvents = (order) => {
181
- setTimelineEvents((prevEvents) => {
182
- const reversedEvents = [...prevEvents].reverse();
183
- return applyAvatarLogic(reversedEvents);
184
- });
284
+ const events = order === 'Newest to Oldest' ? [...originalTimelineEvents] : [...originalTimelineEvents].reverse();
285
+ setTimelineEvents(applyAvatarLogic(events));
185
286
  };
186
287
  const applyAvatarLogic = (events) => {
187
288
  let previousUser = null;
@@ -196,6 +297,7 @@ const Timeline = ({ caseNumber }) => {
196
297
  function fetchHistory() {
197
298
  return __awaiter(this, void 0, void 0, function* () {
198
299
  setIsFetchingData(true);
300
+ setFetchError(null);
199
301
  const options = {
200
302
  sortField: 'createdDate',
201
303
  sortOrder: 'DESC',
@@ -205,9 +307,11 @@ const Timeline = ({ caseNumber }) => {
205
307
  const response = yield caseHistory.getHistoryv1(caseNumber, options);
206
308
  const transformedData = transformApiResponseToTimelineData(response);
207
309
  setTimelineEvents(transformedData);
310
+ setOriginalTimelineEvents(transformedData);
208
311
  }
209
312
  catch (error) {
210
313
  console.error('Failed to fetch history:', error);
314
+ setFetchError('Unable to connect');
211
315
  }
212
316
  finally {
213
317
  setIsFetchingData(false);
@@ -215,27 +319,51 @@ const Timeline = ({ caseNumber }) => {
215
319
  });
216
320
  }
217
321
  fetchHistory();
218
- // eslint-disable-next-line react-hooks/exhaustive-deps
219
322
  }, [caseNumber]);
220
- //Using useLayoutEffect because we are measuring styles based on DOM, useLayoutEffect runs after DOM Mutations https://react.dev/reference/react/useLayoutEffect
221
- useLayoutEffect(() => {
222
- const updateLineStyle = debounce(() => {
223
- var _a;
323
+ const futureDateValidator = (date) => {
324
+ const today = new Date();
325
+ today.setHours(0, 0, 0, 0); // Normalize to midnight
326
+ if (date > today) {
327
+ return 'Date cannot be in the future.';
328
+ }
329
+ return '';
330
+ };
331
+ const updateLineStyle = () => {
332
+ const timelineElement = document.querySelector('.timeline');
333
+ if (timelineElement) {
224
334
  if (lastNodeRef.current) {
225
- const timelineBottom = lastNodeRef.current.getBoundingClientRect().bottom + window.scrollY;
226
- const timelineElement = document.querySelector('.timeline');
227
- const avatarHeight = ((_a = lastNodeRef.current.querySelector('.timeline-avatar')) === null || _a === void 0 ? void 0 : _a.clientHeight) || 0;
228
- if (timelineElement) {
229
- // Get the timeline element's position relative to the document
230
- const timelineTop = timelineElement.getBoundingClientRect().top + window.scrollY;
231
- // Calculate value relative to the timeline's top must be adjusted for page scroll or we get random bottom value based on where user scrolled before execution
232
- const calculatedBottomValue = `${timelineTop + timelineElement.offsetHeight - timelineBottom + avatarHeight}px`;
233
- timelineElement.style.setProperty('--timeline-bottom', calculatedBottomValue);
234
- }
335
+ const timelineRect = timelineElement.getBoundingClientRect();
336
+ const lastNodeRect = lastNodeRef.current.getBoundingClientRect();
337
+ const timelineBottomPosition = timelineRect.bottom + window.scrollY;
338
+ const lastNodeBottomPosition = lastNodeRect.bottom + window.scrollY;
339
+ // Calculate the difference between the bottom of the timeline and the bottom of the last node
340
+ const bottomOffset = timelineBottomPosition - lastNodeBottomPosition;
341
+ // Set the --timeline-bottom CSS variable to adjust the line height
342
+ timelineElement.style.setProperty('--timeline-bottom', `${bottomOffset}px`);
343
+ }
344
+ else {
345
+ timelineElement.style.setProperty('--timeline-bottom', '0px');
346
+ }
347
+ }
348
+ };
349
+ //Using useLayoutEffect because we are measuring styles based on DOM, useLayoutEffect runs after DOM Mutations https://react.dev/reference/react/useLayoutEffect
350
+ useEffect(() => {
351
+ const debouncedUpdateLineStyle = debounce(updateLineStyle, 100);
352
+ window.addEventListener('resize', debouncedUpdateLineStyle);
353
+ if (!isFetchingData && filteredEvents.length > 0) {
354
+ requestAnimationFrame(() => {
355
+ updateLineStyle();
356
+ });
357
+ }
358
+ else {
359
+ // No events or still fetching, set --timeline-bottom to 0px
360
+ const timelineElement = document.querySelector('.timeline');
361
+ if (timelineElement) {
362
+ timelineElement.style.setProperty('--timeline-bottom', '0px');
235
363
  }
236
- }, 100);
237
- updateLineStyle();
238
- }, [timelineEvents, paginatedListLength, dateValue]);
364
+ }
365
+ return () => window.removeEventListener('resize', debouncedUpdateLineStyle);
366
+ }, [isFetchingData, filteredEvents, page, perPage]);
239
367
  const handleSearchChange = (value) => {
240
368
  setSearchValue(value.target.value);
241
369
  };
@@ -251,60 +379,64 @@ const Timeline = ({ caseNumber }) => {
251
379
  const lowercasedFilter = searchValue.toLowerCase();
252
380
  filteredBySearch = filteredBySearch.filter(({ left, right }) => {
253
381
  const dateString = left.date.toLowerCase();
382
+ const timeString = left.text.toLowerCase();
383
+ const rightDateString = right.date.toLowerCase();
254
384
  return (dateString.includes(lowercasedFilter) ||
255
- left.text.toLocaleLowerCase().includes(lowercasedFilter) ||
256
- right.date.toLowerCase().includes(lowercasedFilter));
385
+ timeString.includes(lowercasedFilter) ||
386
+ rightDateString.includes(lowercasedFilter));
257
387
  });
258
388
  }
259
389
  let finalFilteredEvents = filteredBySearch;
260
390
  if (dateValue) {
261
391
  finalFilteredEvents = filteredBySearch.filter(({ left }) => {
262
- // Format the event date to match the DatePicker format (YYYY-MM-DD) We need to do it this way so we dont have discrepancy (off by one month when selecting) due to the way that JS handles timezones
263
- const eventDateFormatted = new Date(left.date).toISOString().split('T')[0];
264
- // Compare the formatted event date with the selected date from DatePicker
265
- return eventDateFormatted === dateValue;
392
+ const eventDate = parse(left.date, 'MMMM d, yyyy', new Date());
393
+ const selectedDate = parse(dateValue, 'yyyy-MM-dd', new Date());
394
+ return isSameDay(eventDate, selectedDate);
266
395
  });
267
396
  }
268
397
  setFilteredEvents(finalFilteredEvents);
398
+ setPage(1);
269
399
  }, [searchValue, dateValue, timelineEvents]);
270
- if (filteredEvents.length < 1 && !isFetchingData) {
271
- return (React.createElement("div", { key: "no-results" },
272
- React.createElement("div", { className: "timelineMenu" },
273
- React.createElement(SearchInput, { placeholder: t('Search for a user, action, or keyword'), value: searchValue, onChange: handleSearchChange, onClear: handleClear, className: "case-history-timeline-search" }),
274
- React.createElement("div", { className: "case-history-timeline-datepicker" },
275
- React.createElement(DatePicker, { value: dateValue, onChange: (_event, value) => handleDateChange(value) }),
276
- React.createElement(Button, { onClick: () => setDateValue(''), isDisabled: isEmpty(dateValue) }, "Reset"))),
277
- React.createElement("div", { className: "timeline-sort-order-select" },
278
- React.createElement(Select, { id: "order-select", isOpen: isSelectOpen, selected: selectedOrder, onSelect: handleSelect, onOpenChange: (isOpen) => setIsSelectOpen(isOpen), toggle: (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: handleSelectToggle, isExpanded: isSelectOpen, style: { width: '200px' } }, selectedOrder)), shouldFocusToggleOnSelect: true },
279
- React.createElement(SelectList, null,
280
- React.createElement(SelectOption, { value: "Newest to Oldest" }, "Newest to Oldest"),
281
- React.createElement(SelectOption, { value: "Oldest to Newest" }, "Oldest to Newest")))),
282
- React.createElement(EmptyState, { variant: EmptyStateVariant.full },
400
+ const handleReload = (e) => {
401
+ e.preventDefault();
402
+ window.location.href = window.location.href;
403
+ window.location.reload();
404
+ };
405
+ const renderTimelineContent = () => {
406
+ if (fetchError) {
407
+ return (React.createElement(EmptyState, { variant: EmptyStateVariant.full },
408
+ React.createElement(EmptyStateHeader, { titleText: "Unable to connect", icon: React.createElement(EmptyStateIcon, { icon: ExclamationCircleIcon, color: globalDangerColor100.value }), headingLevel: "h2" }),
409
+ React.createElement(EmptyStateBody, null,
410
+ React.createElement(Trans, null,
411
+ "Try ",
412
+ React.createElement("a", { href: "#", onClick: handleReload }, "reloading the page"),
413
+ ' ',
414
+ "or check back later"))));
415
+ }
416
+ if (filteredEvents.length < 1) {
417
+ return (React.createElement(EmptyState, { variant: EmptyStateVariant.full },
283
418
  React.createElement(EmptyStateHeader, { titleText: React.createElement(Trans, null, "No results found"), icon: React.createElement(EmptyStateIcon, { icon: SearchIcon }), headingLevel: "h2" }),
284
419
  React.createElement(EmptyStateBody, null,
285
- React.createElement(Trans, null, "Try modifying your search query or changing the date range and try again.")))));
286
- }
420
+ React.createElement(Trans, null, "Try modifying your search query or changing the date range and try again."))));
421
+ }
422
+ const paginatedEvents = filteredEvents.slice((page - 1) * perPage, page * perPage);
423
+ return (React.createElement(React.Fragment, null, paginatedEvents.map((node, index) => (React.createElement(TimelineNode, { key: index, leftEvent: node.left, rightEvent: node.right, user: node.right.user, useAvatar: node.right.useAvatar, ref: index === paginatedEvents.length - 1 ? lastNodeRef : null })))));
424
+ };
287
425
  return (React.createElement(React.Fragment, null,
288
426
  React.createElement("div", { className: "timelineMenu" },
289
- React.createElement(SearchInput, { placeholder: "Search for a user, action, or keyword", value: searchValue, onChange: handleSearchChange, onClear: handleClear, className: "case-history-timeline-search" }),
427
+ React.createElement(SearchInput, { placeholder: t('Search for a user, action, or keyword'), value: searchValue, onChange: handleSearchChange, onClear: handleClear, className: "case-history-timeline-search" }),
290
428
  React.createElement("div", { className: "case-history-timeline-datepicker" },
291
- React.createElement(DatePicker, { value: dateValue, onChange: (_event, value) => handleDateChange(value) }),
429
+ React.createElement(DatePicker, { value: dateValue, onChange: (_event, value) => handleDateChange(value), validators: [futureDateValidator] }),
292
430
  React.createElement(Button, { onClick: () => setDateValue(''), isDisabled: isEmpty(dateValue) }, "Reset"))),
293
- React.createElement("div", { className: "timeline-sort-order-select" },
294
- React.createElement(Select, { id: "order-select", isOpen: isSelectOpen, selected: selectedOrder, onSelect: handleSelect, onOpenChange: (isOpen) => setIsSelectOpen(isOpen), toggle: (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: handleSelectToggle, isExpanded: isSelectOpen, style: { width: '200px' } }, selectedOrder)), shouldFocusToggleOnSelect: true },
295
- React.createElement(SelectList, null,
431
+ React.createElement("div", { className: "timeline-controls" },
432
+ React.createElement("div", { className: "timeline-pagination" }, renderPagination()),
433
+ React.createElement("div", { className: "timeline-sort-order-select" },
434
+ React.createElement(Select, { id: "order-select", isOpen: isSelectOpen, selected: selectedOrder, onSelect: handleSelect, onOpenChange: (isOpen) => setIsSelectOpen(isOpen), shouldFocusToggleOnSelect: false, toggle: (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: handleSelectToggle, isExpanded: isSelectOpen, style: { width: '200px' } }, selectedOrder)) },
296
435
  React.createElement(SelectOption, { value: "Newest to Oldest" }, "Newest to Oldest"),
297
436
  React.createElement(SelectOption, { value: "Oldest to Newest" }, "Oldest to Newest")))),
298
- React.createElement("div", { className: "timeline" },
299
- isFetchingData && (React.createElement("div", { className: "timeline-loading-spinner" },
300
- React.createElement(Spinner, { size: "xl" }))),
301
- !isFetchingData && (React.createElement(PaginatedList, { id: "case-history-paginated-timeline", listItems: filteredEvents &&
302
- filteredEvents.map((node, index) => (React.createElement(TimelineNode, { key: index, leftEvent: node.left, rightEvent: node.right, user: node.right.user, useAvatar: node.right.useAvatar, ref: index === (paginatedListLength && paginatedListLength - 1) ? lastNodeRef : null //this ref is needed to perform calculations on the final node height
303
- }))), perPage: 20, perPageOptions: [
304
- { title: '5', value: 5 },
305
- { title: '10', value: 10 },
306
- { title: '15', value: 15 },
307
- { title: '20', value: 20 },
308
- ], setPaginatedListLength: setPaginatedListLength })))));
437
+ isFetchingData ? (React.createElement("div", { className: "timeline-loading-spinner" },
438
+ React.createElement(Spinner, { size: "xl" }))) : (React.createElement("div", { className: `timeline ${filteredEvents.length === 0 ? 'timeline-empty' : ''}` }, renderTimelineContent())),
439
+ React.createElement("div", { className: "timeline-controls" },
440
+ React.createElement("div", { className: "timeline-pagination" }, renderPagination()))));
309
441
  };
310
442
  export default Timeline;
@@ -1 +1 @@
1
- {"version":3,"file":"Fts.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseInformation/Fts.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAS/D,UAAU,MAAM;IACZ,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAQD,iBAAS,GAAG,CAAC,KAAK,EAAE,MAAM,qBA4OzB;kBA5OQ,GAAG;;;AA+OZ,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"Fts.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseInformation/Fts.tsx"],"names":[],"mappings":"AAWA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAS/D,UAAU,MAAM;IACZ,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAQD,iBAAS,GAAG,CAAC,KAAK,EAAE,MAAM,qBA6OzB;kBA7OQ,GAAG;;;AAgPZ,eAAe,GAAG,CAAC"}
@@ -100,10 +100,12 @@ function Fts(props) {
100
100
  setCaseState(caseDispatch, { ftsContactSameAsOwner: e.target.checked });
101
101
  if (!e.target.checked) {
102
102
  setFtsContact('');
103
- return;
103
+ setContactInfoIntoGlobalState('');
104
+ }
105
+ else {
106
+ setFtsContact(`${phoneCountryCode}-${phoneAreaCodePrefixLineNumber}`);
107
+ setContactInfoIntoGlobalState(`${phoneCountryCode}-${phoneAreaCodePrefixLineNumber}`);
104
108
  }
105
- setFtsContact(`${phoneCountryCode}-${phoneAreaCodePrefixLineNumber}`);
106
- setContactInfoIntoGlobalState(`${phoneCountryCode}-${phoneAreaCodePrefixLineNumber}`);
107
109
  };
108
110
  const onSave = (e) => __awaiter(this, void 0, void 0, function* () {
109
111
  setContactInfoIntoGlobalState(contactInfo24X7State);
@@ -148,8 +150,7 @@ function Fts(props) {
148
150
  }
149
151
  // eslint-disable-next-line react-hooks/exhaustive-deps
150
152
  }, [phoneAreaCodePrefixLineNumber, phoneCountryCode]);
151
- const disableFtsNContact = !isEmpty(caseNumber) &&
152
- (!isFtsEditableOnEditPage(entitlementSla, severity) || isFtsContactUpdating || isFtsUpdating);
153
+ const disableFtsNContact = !isEmpty(caseNumber) && (!isFtsEditableOnEditPage(entitlementSla, severity) || isFtsUpdating);
153
154
  const maxLengthErrorMessage = t('Contact information cannot be more than {{limit}} characters.', {
154
155
  limit: CONTACT_INFO_24X7_LIMIT,
155
156
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Cep.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseManagement/Cep.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAoB/D,UAAU,MAAM;CAAG;AAEnB,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,qBAsThC"}
1
+ {"version":3,"file":"Cep.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseManagement/Cep.tsx"],"names":[],"mappings":"AAuBA,OAAO,KAA0C,MAAM,OAAO,CAAC;AAoB/D,UAAU,MAAM;CAAG;AAEnB,wBAAgB,GAAG,CAAC,KAAK,EAAE,MAAM,qBAuThC"}
@@ -66,8 +66,9 @@ export function Cep(props) {
66
66
  // edit mode - set cep to false
67
67
  yield confirm({
68
68
  catchOnCancel: true,
69
- title: t(`Updating Consultant Engagement in Progress`),
70
- description: t('Are you sure?'),
69
+ title: t(`No consultant engaged?`),
70
+ description: t("You're confirming that you don't have a consultant engaged on this ticket."),
71
+ confirmText: t('Yes, confirm'),
71
72
  });
72
73
  cepUpdate({ cep: false });
73
74
  clearCepFormInputs();
@@ -1 +1 @@
1
- {"version":3,"file":"RHAssociatesSelector.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseManagement/RHAssociatesSelector.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAS5D,MAAM,WAAW,MAAM;CAAG;AAG1B,iBAAS,oBAAoB,CAAC,KAAK,EAAE,MAAM,qBAqM1C;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}
1
+ {"version":3,"file":"RHAssociatesSelector.d.ts","sourceRoot":"","sources":["../../../../src/components/CaseManagement/RHAssociatesSelector.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAS5D,MAAM,WAAW,MAAM;CAAG;AAG1B,iBAAS,oBAAoB,CAAC,KAAK,EAAE,MAAM,qBA+M1C;AAED,OAAO,EAAE,oBAAoB,EAAE,CAAC"}