@rh-support/troubleshoot 2.6.295 → 2.6.299

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 (98) hide show
  1. package/lib/esm/components/CaseEditView/ActiveCustomerEscalation/RequestEscalationModal.d.ts.map +1 -1
  2. package/lib/esm/components/CaseEditView/ActiveCustomerEscalation/RequestEscalationModal.js +4 -7
  3. package/lib/esm/components/CaseEditView/Case.d.ts.map +1 -1
  4. package/lib/esm/components/CaseEditView/Case.js +42 -12
  5. package/lib/esm/components/CaseEditView/CaseDetailsTabs.d.ts.map +1 -1
  6. package/lib/esm/components/CaseEditView/CaseDetailsTabs.js +17 -7
  7. package/lib/esm/components/CaseEditView/CaseOverview/CaseStatus.d.ts +1 -2
  8. package/lib/esm/components/CaseEditView/CaseOverview/CaseStatus.d.ts.map +1 -1
  9. package/lib/esm/components/CaseEditView/CaseOverview/CaseStatus.js +8 -6
  10. package/lib/esm/components/CaseEditView/CaseOverview/index.d.ts.map +1 -1
  11. package/lib/esm/components/CaseEditView/CaseOverview/index.js +17 -12
  12. package/lib/esm/components/CaseEditView/ConfirmationModals/ReopenCaseModal.d.ts +1 -3
  13. package/lib/esm/components/CaseEditView/ConfirmationModals/ReopenCaseModal.d.ts.map +1 -1
  14. package/lib/esm/components/CaseEditView/ConfirmationModals/ReopenCaseModal.js +3 -4
  15. package/lib/esm/components/CaseEditView/RequestRemoteSession/ESSRemoteSessionModal.d.ts.map +1 -1
  16. package/lib/esm/components/CaseEditView/RequestRemoteSession/ESSRemoteSessionModal.js +4 -7
  17. package/lib/esm/components/CaseEditView/RequestRemoteSession/NewEssTermsModal.d.ts.map +1 -1
  18. package/lib/esm/components/CaseEditView/RequestRemoteSession/NewEssTermsModal.js +4 -30
  19. package/lib/esm/components/CaseEditView/RequestRemoteSession/RemoteSessionAgreementModal.d.ts.map +1 -1
  20. package/lib/esm/components/CaseEditView/RequestRemoteSession/RemoteSessionAgreementModal.js +4 -30
  21. package/lib/esm/components/CaseEditView/RequestRemoteSession/remoteSessionUtils.d.ts +3 -0
  22. package/lib/esm/components/CaseEditView/RequestRemoteSession/remoteSessionUtils.d.ts.map +1 -0
  23. package/lib/esm/components/CaseEditView/RequestRemoteSession/remoteSessionUtils.js +22 -0
  24. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.d.ts +4 -2
  25. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.d.ts.map +1 -1
  26. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.js +110 -148
  27. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useAttachmentUploadCompletion.d.ts +15 -0
  28. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useAttachmentUploadCompletion.d.ts.map +1 -0
  29. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useAttachmentUploadCompletion.js +19 -0
  30. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.d.ts +17 -0
  31. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.d.ts.map +1 -0
  32. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.js +49 -0
  33. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment.d.ts +6 -2
  34. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment.d.ts.map +1 -1
  35. package/lib/esm/components/CaseEditView/Tabs/CaseDiscussion/PostComment.js +147 -241
  36. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.d.ts.map +1 -1
  37. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/CaseHistory.js +3 -1
  38. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.css +4 -4
  39. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.d.ts +35 -3
  40. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.d.ts.map +1 -1
  41. package/lib/esm/components/CaseEditView/Tabs/CaseHistory/Timeline.js +63 -224
  42. package/lib/esm/components/CaseEditView/Tabs/CaseSummary/CaseSummary.d.ts.map +1 -1
  43. package/lib/esm/components/CaseEditView/Tabs/CaseSummary/CaseSummary.js +7 -2
  44. package/lib/esm/components/CaseEditView/Tabs/RMEEscalations/RMEEscalationList.d.ts.map +1 -1
  45. package/lib/esm/components/CaseEditView/Tabs/RMEEscalations/RMEEscalationList.js +4 -7
  46. package/lib/esm/components/CaseManagement/Cep.d.ts.map +1 -1
  47. package/lib/esm/components/CaseManagement/Cep.js +4 -7
  48. package/lib/esm/components/ProductSelector/NewProductVersionSelector.d.ts.map +1 -1
  49. package/lib/esm/components/ProductSelector/NewProductVersionSelector.js +18 -4
  50. package/lib/esm/components/UpdateSeverityModal/UpdateSeverityModal.d.ts.map +1 -1
  51. package/lib/esm/components/UpdateSeverityModal/UpdateSeverityModal.js +4 -7
  52. package/lib/esm/components/shared/usePostComment.d.ts.map +1 -1
  53. package/lib/esm/components/shared/usePostComment.js +6 -10
  54. package/lib/esm/components/wizardLayout/GlobalTroubleshootEffects.d.ts.map +1 -1
  55. package/lib/esm/components/wizardLayout/GlobalTroubleshootEffects.js +0 -2
  56. package/lib/esm/context/CaseContext.d.ts.map +1 -1
  57. package/lib/esm/hooks/useCaseComments/caseCommentMutation.d.ts +4 -0
  58. package/lib/esm/hooks/useCaseComments/caseCommentMutation.d.ts.map +1 -0
  59. package/lib/esm/hooks/useCaseComments/caseCommentMutation.js +12 -0
  60. package/lib/esm/hooks/useCaseComments/caseCommentsQuery.d.ts +4 -0
  61. package/lib/esm/hooks/useCaseComments/caseCommentsQuery.d.ts.map +1 -0
  62. package/lib/esm/hooks/useCaseComments/caseCommentsQuery.js +55 -0
  63. package/lib/esm/hooks/useCaseComments/index.d.ts +7 -0
  64. package/lib/esm/hooks/useCaseComments/index.d.ts.map +1 -0
  65. package/lib/esm/hooks/useCaseComments/index.js +5 -0
  66. package/lib/esm/hooks/useCaseComments/mapCaseComment.d.ts +25 -0
  67. package/lib/esm/hooks/useCaseComments/mapCaseComment.d.ts.map +1 -0
  68. package/lib/esm/hooks/useCaseComments/mapCaseComment.js +66 -0
  69. package/lib/esm/hooks/useCaseComments/types.d.ts +80 -0
  70. package/lib/esm/hooks/useCaseComments/types.d.ts.map +1 -0
  71. package/lib/esm/hooks/useCaseComments/types.js +1 -0
  72. package/lib/esm/hooks/useCaseComments/useCaseComments.d.ts +16 -0
  73. package/lib/esm/hooks/useCaseComments/useCaseComments.d.ts.map +1 -0
  74. package/lib/esm/hooks/useCaseComments/useCaseComments.js +25 -0
  75. package/lib/esm/hooks/useCaseComments/useCreateCaseComment.d.ts +19 -0
  76. package/lib/esm/hooks/useCaseComments/useCreateCaseComment.d.ts.map +1 -0
  77. package/lib/esm/hooks/useCaseComments/useCreateCaseComment.js +33 -0
  78. package/lib/esm/hooks/useCaseHistory.d.ts +26 -0
  79. package/lib/esm/hooks/useCaseHistory.d.ts.map +1 -0
  80. package/lib/esm/hooks/useCaseHistory.js +98 -0
  81. package/lib/esm/models/caseHistory.d.ts +80 -0
  82. package/lib/esm/models/caseHistory.d.ts.map +1 -0
  83. package/lib/esm/models/caseHistory.js +1 -0
  84. package/lib/esm/reducers/CaseConstNTypes.d.ts +0 -1
  85. package/lib/esm/reducers/CaseConstNTypes.d.ts.map +1 -1
  86. package/lib/esm/reducers/CaseConstNTypes.js +0 -1
  87. package/lib/esm/reducers/CaseDiscussionTabReducer.d.ts +0 -2
  88. package/lib/esm/reducers/CaseDiscussionTabReducer.d.ts.map +1 -1
  89. package/lib/esm/reducers/CaseDiscussionTabReducer.js +1 -28
  90. package/lib/esm/reducers/CaseReducer.d.ts.map +1 -1
  91. package/lib/esm/reducers/CaseReducer.js +14 -4
  92. package/lib/esm/utils/TimelineUtils.d.ts +41 -0
  93. package/lib/esm/utils/TimelineUtils.d.ts.map +1 -0
  94. package/lib/esm/utils/TimelineUtils.js +134 -0
  95. package/lib/esm/utils/caseHistoryUtils.d.ts +66 -0
  96. package/lib/esm/utils/caseHistoryUtils.d.ts.map +1 -0
  97. package/lib/esm/utils/caseHistoryUtils.js +165 -0
  98. package/package.json +5 -5
@@ -0,0 +1,22 @@
1
+ export const getLocaleNameFromLocalCode = (localeCode) => {
2
+ if ('Intl' in window && 'DisplayNames' in Intl) {
3
+ try {
4
+ const [lang, locale] = localeCode.includes('_') ? localeCode.split('_') : localeCode.split('-');
5
+ // https://github.com/microsoft/TypeScript/issues/41338
6
+ const langName = new Intl.DisplayNames([lang], { type: 'language' });
7
+ const regionNamesInLang = new Intl.DisplayNames([lang], { type: 'region' });
8
+ return `${regionNamesInLang.of(locale)} (${langName.of(lang)})`;
9
+ }
10
+ catch (e) {
11
+ return localeCode;
12
+ }
13
+ }
14
+ return localeCode;
15
+ };
16
+ export const formatSessionLinks = (sessionLink) => {
17
+ const links = sessionLink
18
+ .split(/[;,| \n\r\t]+/)
19
+ .map((link) => link.trim())
20
+ .filter(Boolean);
21
+ return links.map((link) => `[${link}](${link})`).join('\n');
22
+ };
@@ -1,12 +1,14 @@
1
- import { ICaseChat, ICaseComment } from '@cee-eng/hydrajs/@types/api/public/case';
1
+ import { ICaseComment } from '@cee-eng/hydrajs/@types/api/public/case';
2
2
  import React from 'react';
3
3
  interface IProps {
4
4
  caseNumber: string;
5
5
  caseComments: ICaseComment[];
6
- caseChats: ICaseChat[];
6
+ commentsLoading?: boolean;
7
+ commentsError?: unknown;
7
8
  accountNumber: string;
8
9
  tabRef: React.MutableRefObject<HTMLDivElement>;
9
10
  isDiscussionTab: boolean;
11
+ refetchComments: () => Promise<unknown> | void;
10
12
  }
11
13
  export default function CaseDiscussion(props: IProps): React.JSX.Element;
12
14
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"CaseDiscussion.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAA0B,MAAM,yCAAyC,CAAC;AAkB1G,OAAO,KAAoE,MAAM,OAAO,CAAC;AAyCzF,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC/C,eAAe,EAAE,OAAO,CAAC;CAC5B;AAOD,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,KAAK,EAAE,MAAM,qBA4oBnD"}
1
+ {"version":3,"file":"CaseDiscussion.d.ts","sourceRoot":"","sources":["../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/CaseDiscussion.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,yCAAyC,CAAC;AAcvE,OAAO,KAAoE,MAAM,OAAO,CAAC;AAwCzF,UAAU,MAAM;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,EAAE,CAAC;IAC7B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IAGxB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC/C,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CAClD;AAOD,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,KAAK,EAAE,MAAM,qBAmhBnD"}
@@ -7,12 +7,13 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { caseAttachments } from '@cee-eng/hydrajs';
10
+ // TODO: Re-enable after GraphQL migration of chats, external trackers, attachments
11
+ // import { ICaseChat, IExternalTrackerUpdate } from '@cee-eng/hydrajs/@types/api/public/case';
12
+ // import { IAttachment } from '@cee-eng/hydrajs/@types/models/attachment';
11
13
  import { MenuToggle, Select, SelectList, SelectOption } from '@patternfly/react-core';
12
- import { AlertMessage, AlertType, ErrorBoundary, LoadingIndicator, ToastNotification } from '@rh-support/components';
14
+ import { AlertMessage, AlertType, ErrorBoundary, LoadingIndicator } from '@rh-support/components';
13
15
  import { useGlobalStateContext } from '@rh-support/react-context';
14
- import { AbilityContext, CaseDetailsFields, resourceActions, resources } from '@rh-support/user-permissions';
15
- import { dtmTrackEventCaseStepEncountered, getDropdownBtnPlaceholder, getUrlParsedParams, scrollIntoView, } from '@rh-support/utils';
16
+ import { dtmTrackEventCaseStepEncountered, getUrlParsedParams, scrollIntoView } from '@rh-support/utils';
16
17
  import cloneDeep from 'lodash/cloneDeep';
17
18
  import filter from 'lodash/filter';
18
19
  import findIndex from 'lodash/findIndex';
@@ -24,18 +25,27 @@ import { Trans, useTranslation } from 'react-i18next';
24
25
  import { useLocation } from 'react-router-dom';
25
26
  import { AutoSizer, CellMeasurer, CellMeasurerCache, List, WindowScroller } from 'react-virtualized';
26
27
  import { useCaseDispatch, useCaseSelector } from '../../../../context/CaseContext';
27
- import { useCaseDetailsPageDispatchContext, useCaseDetailsPageStateContext, } from '../../../../context/CaseDetailsPageContext';
28
28
  import { useCaseDiscussionTabDispatchContext, useCaseDiscussionTabStateContext, } from '../../../../context/CaseDiscussionTabContext';
29
- import { clearReplyComment, discussionSortOrderConst, DiscussionType, setCommentText, setDiscussions, setFilter, setSort, updateAttachment, updateAttacmentsAfterRemoval, updateCommentStateFeedback, updateDiscussionStateAttachments, updateDiscussionStateComments, } from '../../../../reducers/CaseDiscussionTabReducer';
29
+ import { clearReplyComment, discussionSortOrderConst, DiscussionType, setCommentText, setDiscussions, setSort,
30
+ // TODO: Re-enable after GraphQL migration
31
+ // setFilter,
32
+ // updateAttachment,
33
+ // updateAttacmentsAfterRemoval,
34
+ // updateCommentStateFeedback,
35
+ // updateDiscussionStateAttachments,
36
+ } from '../../../../reducers/CaseDiscussionTabReducer';
30
37
  import { setCaseState } from '../../../../reducers/CaseReducer';
31
- import { AttachmentContextProvider } from '../../../shared/fileUpload/reducer/AttachmentReducerContext';
38
+ // TODO: Re-enable after GraphQL migration of attachments
39
+ // import { AttachmentContextProvider } from '../../../shared/fileUpload/reducer/AttachmentReducerContext';
32
40
  import { PDFContext } from '../../PDFContainer';
33
- import CaseAttachment from './CaseAttachment';
34
- import CaseChat from './CaseChat';
41
+ // TODO: Re-enable after GraphQL migration
42
+ // import CaseAttachment from './CaseAttachment';
43
+ // import CaseChat from './CaseChat';
35
44
  import CaseComments from './CaseComments';
36
- import CaseExternalTrackerUpdate from './CaseExternalTrackerUpdate';
45
+ // import CaseExternalTrackerUpdate from './CaseExternalTrackerUpdate';
37
46
  import { CommentSearch } from './CommentSearch';
38
47
  import { PostComment } from './PostComment';
48
+ /** Threshold above which comments render in a virtualized (windowed) list for performance. */
39
49
  const VC_MINIMUM_COMMENT = 150;
40
50
  let cellMeasureCache = new CellMeasurerCache({
41
51
  fixedWidth: true,
@@ -43,30 +53,20 @@ let cellMeasureCache = new CellMeasurerCache({
43
53
  });
44
54
  export default function CaseDiscussion(props) {
45
55
  const { t } = useTranslation();
46
- const { isPostingRemoteRequestCommentCompleted, screenSessionRequested, product, version } = useCaseSelector((state) => ({
56
+ const { isPostingRemoteRequestCommentCompleted, product, version } = useCaseSelector((state) => ({
47
57
  isPostingRemoteRequestCommentCompleted: state.isPostingRemoteRequestCommentCompleted,
48
- screenSessionRequested: state.caseDetails.screenSessionRequested,
49
58
  product: state.caseDetails.product,
50
59
  version: state.caseDetails.version,
51
60
  }), isEqual);
52
61
  const urlSearchParams = getUrlParsedParams(useLocation().search);
53
- const discussionIdFromUrl = ((urlSearchParams === null || urlSearchParams === void 0 ? void 0 : urlSearchParams.commentId) ||
54
- (urlSearchParams === null || urlSearchParams === void 0 ? void 0 : urlSearchParams.externalTrackerId) ||
55
- (urlSearchParams === null || urlSearchParams === void 0 ? void 0 : urlSearchParams.attachmentId) ||
56
- (urlSearchParams === null || urlSearchParams === void 0 ? void 0 : urlSearchParams.chatId));
62
+ const discussionIdFromUrl = urlSearchParams === null || urlSearchParams === void 0 ? void 0 : urlSearchParams.commentId;
57
63
  const activeDiscussionElementRef = useRef(null);
58
- const { sort, discussionFiltersListState, allDiscussions, replyCommentText, replyCommentCreator } = useCaseDiscussionTabStateContext();
59
- const { globalMetadataState: { loggedInUsersAccount, navBarRef }, } = useGlobalStateContext();
64
+ const { sort, allDiscussions, replyCommentText, replyCommentCreator } = useCaseDiscussionTabStateContext();
65
+ const { globalMetadataState: { navBarRef }, } = useGlobalStateContext();
60
66
  const dispatchDiscussion = useCaseDiscussionTabDispatchContext();
61
67
  const caseDispatch = useCaseDispatch();
62
- const { caseDetailsPageState: { caseFeedbacksHydra, caseAttachmentHydra, caseExternalTrackersUpdate }, } = useCaseDetailsPageStateContext();
63
- const dispatchCaseDetailsHydraAttachments = useCaseDetailsPageDispatchContext();
64
68
  const { caseNumber } = props;
65
69
  const activeElementId = useRef(null);
66
- const ability = useContext(AbilityContext);
67
- const canSeeExternalTracker = ability.can(resourceActions.PATCH, resources.CASE_DETAILS, CaseDetailsFields.CASE_DETAILS_EXTERNAL_TRACKER);
68
- const canSeeChat = ability.can(resourceActions.PATCH, resources.CASE_DETAILS, CaseDetailsFields.CASE_DETAILS_CHAT);
69
- const canSeeAttachments = ability.can(resourceActions.PATCH, resources.CASE_DETAILS, CaseDetailsFields.CASE_DETAILS_SEE_ATTACHMENTS);
70
70
  const listRef = React.useRef(null);
71
71
  const windowScrollerRef = React.useRef(null);
72
72
  const commentRef = React.useRef(null);
@@ -78,10 +78,7 @@ export default function CaseDiscussion(props) {
78
78
  const [isJumpToCommentClicked, setJumpToCommentClicked] = useState(false);
79
79
  const [canUpdateScrollTop, setCanUpdateScrollTop] = useState(true);
80
80
  const [replyingToPrivateComment, setReplyingToPrivateComment] = useState(null);
81
- // used for tracking comment section width changes
82
81
  const [discussionSectionWidth, setDiscussionSectionWidth] = useState(null);
83
- const [isOpen, setIsOpen] = useState(false);
84
- const [selectedFilterItems, setSelectedFilterItems] = useState([]);
85
82
  const [isSortOpen, setIsSortOpen] = useState(false);
86
83
  const [sortSelected, setSortSelected] = useState('Select a value');
87
84
  if (props.isDiscussionTab) {
@@ -90,18 +87,10 @@ export default function CaseDiscussion(props) {
90
87
  defaultHeight: 100,
91
88
  });
92
89
  }
93
- const computeFilterComment = () => {
94
- return filter(allDiscussions, (discussion) => {
95
- const activeFilter = discussionFiltersListState.filter((filter) => filter.value === discussion.discussionType && filter.isSelected);
96
- return activeFilter.length === 1 ? discussion : null;
97
- });
98
- };
99
90
  const shouldRenderRVComponent = !isExportingPDF && allDiscussions.length > VC_MINIMUM_COMMENT;
100
- // filter comments on the basis of filterBy and search api
101
- const computeFilteredComments = computeFilterComment();
102
91
  const comuteSearchedComments = searchResult.length === 0
103
- ? cloneDeep(computeFilteredComments)
104
- : filter([...computeFilteredComments], (o) => { var _a; return searchResult.indexOf((_a = o === null || o === void 0 ? void 0 : o.item) === null || _a === void 0 ? void 0 : _a.id) !== -1; });
92
+ ? cloneDeep(allDiscussions)
93
+ : filter([...allDiscussions], (o) => { var _a; return searchResult.indexOf((_a = o === null || o === void 0 ? void 0 : o.item) === null || _a === void 0 ? void 0 : _a.id) !== -1; });
105
94
  const isSearchResultEmpty = !isEmpty(searchCommentText) && (searchResult.length === 0 || comuteSearchedComments.length === 0);
106
95
  const setVRCommentIndex = (cId) => {
107
96
  setCommentIndex(cId < 0 ? 0 : cId);
@@ -120,8 +109,6 @@ export default function CaseDiscussion(props) {
120
109
  if (allDiscussions.length > VC_MINIMUM_COMMENT) {
121
110
  const commentIndex = findIndex([...comuteSearchedComments], (discussion) => discussion.item.id === discussionIdFromUrl);
122
111
  setVRCommentIndex(commentIndex);
123
- // to fix scrolling to comment index when scrollTop value from window scroller is 0
124
- // initial list items
125
112
  if (0 <= commentIndex && commentIndex <= 3) {
126
113
  const offset = 80;
127
114
  const scrollTopValue = ((_a = windowScrollerRef === null || windowScrollerRef === void 0 ? void 0 : windowScrollerRef.current) === null || _a === void 0 ? void 0 : _a._positionFromTop) - offset;
@@ -146,38 +133,24 @@ export default function CaseDiscussion(props) {
146
133
  // eslint-disable-next-line react-hooks/exhaustive-deps
147
134
  }, [discussionIdFromUrl, allDiscussions, activeElementId.current]);
148
135
  useEffect(() => {
149
- if (!caseAttachmentHydra.isFetching && caseAttachmentHydra.isError) {
150
- ToastNotification.addDangerMessage(t(`There was an error loading attachments.`));
151
- }
152
- if (!caseExternalTrackersUpdate.isFetching && caseExternalTrackersUpdate.isError) {
153
- ToastNotification.addDangerMessage(t(`There was an error loading external trackers.`));
154
- }
155
- if (!caseFeedbacksHydra.isFetching && caseFeedbacksHydra.isError) {
156
- console.log(`There was an error loading feedback.`);
157
- }
158
- // Attachments, feedbacks and external tracker updates apis are called in advance
159
- if (!isExportingPDF &&
160
- !caseAttachmentHydra.isFetching &&
161
- !caseFeedbacksHydra.isFetching &&
162
- !caseExternalTrackersUpdate.isFetching) {
163
- setDiscussions(dispatchDiscussion, props.caseComments, props.caseChats, caseAttachmentHydra.data, caseFeedbacksHydra.data, caseExternalTrackersUpdate.data, discussionFiltersListState, canSeeExternalTracker, canSeeChat, canSeeAttachments);
136
+ if (!isExportingPDF) {
137
+ setDiscussions(dispatchDiscussion, props.caseComments, [], // TODO: Re-enable chats after GraphQL migration
138
+ [], // TODO: Re-enable attachments after GraphQL migration
139
+ [], // TODO: Re-enable feedbacks after GraphQL migration
140
+ [], // TODO: Re-enable external trackers after GraphQL migration
141
+ [], false, false, false);
164
142
  }
165
143
  // eslint-disable-next-line react-hooks/exhaustive-deps
166
- }, [props.caseComments, caseAttachmentHydra, props.caseChats, caseFeedbacksHydra, caseExternalTrackersUpdate]);
167
- const onAttachmentRemove = (uuid_removed) => {
168
- updateAttacmentsAfterRemoval(dispatchDiscussion, uuid_removed, allDiscussions, discussionFiltersListState);
169
- };
170
- const onFeedbackUpdate = (comment_id, feedback) => {
171
- updateCommentStateFeedback(dispatchDiscussion, comment_id, feedback, allDiscussions);
172
- };
144
+ }, [props.caseComments]);
173
145
  const onCommentAdded = () => __awaiter(this, void 0, void 0, function* () {
174
- yield updateDiscussionStateComments(dispatchDiscussion, caseDispatch, caseNumber, allDiscussions, sort, caseFeedbacksHydra.data, discussionFiltersListState);
146
+ try {
147
+ yield props.refetchComments();
148
+ }
149
+ catch (e) {
150
+ console.error('Failed to refetch comments:', e);
151
+ }
175
152
  dtmTrackEventCaseStepEncountered('comment', caseNumber, product, version);
176
153
  });
177
- const onAttachmentAdded = () => __awaiter(this, void 0, void 0, function* () {
178
- ToastNotification.addSuccessMessage(t('Attachment(s) uploaded successfully'));
179
- yield updateDiscussionStateAttachments(dispatchDiscussion, caseNumber, allDiscussions, sort, loggedInUsersAccount.data.secureSupport, discussionFiltersListState, dispatchCaseDetailsHydraAttachments);
180
- });
181
154
  const onReply = (commentText, commentCreator, isPublic) => {
182
155
  var _a;
183
156
  setReplyingToPrivateComment(!isPublic);
@@ -191,11 +164,11 @@ export default function CaseDiscussion(props) {
191
164
  clearReplyComment(dispatchDiscussion);
192
165
  setReplyingToPrivateComment(null);
193
166
  };
167
+ /** Strips special chars to prevent regex injection, then HTML-escapes via DOM round-trip to prevent XSS. */
194
168
  const sanitizedSearchInput = (value) => {
195
169
  if (!value)
196
170
  return '';
197
171
  const sanitizedValue = value.replace(/[^a-zA-Z0-9]/g, '');
198
- // To escape HTML-like characters (e.g., <, >, &) in sanitizedValue
199
172
  const div = document.createElement('div');
200
173
  div.textContent = sanitizedValue;
201
174
  return div.innerHTML;
@@ -209,10 +182,10 @@ export default function CaseDiscussion(props) {
209
182
  isCommentSearchCleared && setIsCommentSearchCleared(false);
210
183
  };
211
184
  const highlightSearchResults = (searchTerm) => {
185
+ // Delay allows the DOM (especially virtualized rows) to finish rendering before mark.js can highlight matches.
212
186
  setTimeout(() => {
213
- // to mark attachment link button text
214
187
  if (!isEmpty(sanitizedSearchInput(searchTerm))) {
215
- const sanitizedSearchTerm = sanitizedSearchInput(searchTerm); // Sanitize the search term
188
+ const sanitizedSearchTerm = sanitizedSearchInput(searchTerm);
216
189
  document.querySelectorAll('button.attachment-download-btn').forEach((button) => {
217
190
  var _a, _b;
218
191
  if ((_a = button === null || button === void 0 ? void 0 : button.textContent) === null || _a === void 0 ? void 0 : _a.includes(sanitizedSearchTerm)) {
@@ -226,9 +199,6 @@ export default function CaseDiscussion(props) {
226
199
  done: () => {
227
200
  if (isEmpty(searchTerm))
228
201
  return;
229
- // removed { separateWordSearch: false }
230
- // so the search term is split into individual words, and each word is matched separately.
231
- // Search Term: "frontend developer" Matches: Text containing "frontend" or "developer".
232
202
  markInstance.mark(searchTerm);
233
203
  },
234
204
  });
@@ -240,34 +210,52 @@ export default function CaseDiscussion(props) {
240
210
  highlightSearchResults('');
241
211
  setSearchResult([]);
242
212
  if (cId === discussionIdFromUrl) {
243
- // reason to reset activeElementId if comment id is same in the url on click jump to comment button
244
- // we need to re compute filtered comments after clear search results.
245
- // it will call useEffect and will set virtual comment Index(setVRCommentIndex())
246
213
  activeElementId.current = '';
247
214
  }
248
215
  };
249
- const onMetadataUpdate = (attachmentId, updatedAttachment) => {
250
- updateAttachment(dispatchDiscussion, attachmentId, updatedAttachment, allDiscussions);
251
- };
252
- const downloadAttachment = (_a) => __awaiter(this, [_a], void 0, function* ({ caseNumber, uuid, name }) {
253
- const tempToastId = ToastNotification.addInfoMessage(t('Starting file download'));
254
- try {
255
- yield caseAttachments.downloadAttachmentS3(caseNumber, uuid, name, false, loggedInUsersAccount.data.secureSupport);
256
- }
257
- catch (e) {
258
- ToastNotification.addDangerMessage(t(`Could not download {{filename}}: {{error}}`, {
259
- filename: name,
260
- error: e === null || e === void 0 ? void 0 : e.message,
261
- }));
262
- }
263
- finally {
264
- tempToastId && ToastNotification.clearToast(tempToastId);
265
- }
266
- });
267
- /**
268
- * We need to clear the cell measurer cache if user resizes the window or discussion section width is changed
269
- * so that the height of the rows are re-calculated
270
- */
216
+ // TODO: Re-enable after GraphQL migration of attachments
217
+ // const onAttachmentRemove = (uuid_removed) => {
218
+ // updateAttacmentsAfterRemoval(dispatchDiscussion, uuid_removed, allDiscussions, discussionFiltersListState);
219
+ // };
220
+ // TODO: Re-enable after GraphQL migration of attachments
221
+ // const onAttachmentAdded = async () => {
222
+ // ToastNotification.addSuccessMessage(t('Attachment(s) uploaded successfully'));
223
+ // await updateDiscussionStateAttachments(
224
+ // dispatchDiscussion,
225
+ // caseNumber,
226
+ // allDiscussions,
227
+ // sort,
228
+ // loggedInUsersAccount.data.secureSupport,
229
+ // discussionFiltersListState,
230
+ // dispatchCaseDetailsHydraAttachments
231
+ // );
232
+ // };
233
+ // TODO: Re-enable after GraphQL migration of attachments
234
+ // const onMetadataUpdate = (attachmentId: string, updatedAttachment) => {
235
+ // updateAttachment(dispatchDiscussion, attachmentId, updatedAttachment, allDiscussions);
236
+ // };
237
+ // TODO: Re-enable after GraphQL migration of attachments
238
+ // const downloadAttachment = async ({ caseNumber, uuid, name }) => {
239
+ // const tempToastId = ToastNotification.addInfoMessage(t('Starting file download'));
240
+ // try {
241
+ // await caseAttachments.downloadAttachmentS3(
242
+ // caseNumber,
243
+ // uuid,
244
+ // name,
245
+ // false,
246
+ // loggedInUsersAccount.data.secureSupport
247
+ // );
248
+ // } catch (e) {
249
+ // ToastNotification.addDangerMessage(
250
+ // t(`Could not download {{filename}}: {{error}}`, {
251
+ // filename: name,
252
+ // error: e?.message,
253
+ // })
254
+ // );
255
+ // } finally {
256
+ // tempToastId && ToastNotification.clearToast(tempToastId);
257
+ // }
258
+ // };
271
259
  useEffect(() => {
272
260
  if (discussionSectionWidth && shouldRenderRVComponent) {
273
261
  cellMeasureCache && cellMeasureCache.clearAll();
@@ -282,7 +270,6 @@ export default function CaseDiscussion(props) {
282
270
  return (React.createElement(WindowScroller, { ref: windowScrollerRef, onScroll: handleScroll }, ({ height, registerChild, onChildScroll, isScrolling, scrollTop }) => {
283
271
  return (React.createElement("div", { ref: commentRef, "data-tracking-id": "vr-case-comments" },
284
272
  React.createElement(AutoSizer, { disableHeight: true }, ({ width }) => {
285
- // setTimeout to update state in the next tick and avoid bad setState warnings
286
273
  discussionSectionWidth !== width &&
287
274
  setTimeout(() => setDiscussionSectionWidth(width), 0);
288
275
  return (React.createElement("div", { ref: registerChild },
@@ -295,17 +282,18 @@ export default function CaseDiscussion(props) {
295
282
  const renderCaseCommentElement = (discussion) => {
296
283
  if (isEmpty(discussion) || isEmpty(discussion.item))
297
284
  return null;
298
- return (React.createElement("div", { "data-tracking-id": "discussion-elements", key: discussion.item.id },
299
- discussion.discussionType === DiscussionType.COMMENT && (React.createElement(CaseComments, { caseNumber: caseNumber, ref: discussionIdFromUrl === discussion.item.id ? activeDiscussionElementRef : null, comment: discussion.item, onFeedBackUpdate: onFeedbackUpdate, feedback: discussion.feedback, accountNumber: props.accountNumber, onReply: onReply, showJumpToComment: !isEmpty(searchCommentText), onJumpToComment: onJumpToComment, downloadAttachment: downloadAttachment })),
300
- discussion.discussionType === DiscussionType.ATTACHMENT && (React.createElement(CaseAttachment, { attachment: discussion.item, onAttachmentRemove: onAttachmentRemove, onMetadataUpdate: onMetadataUpdate, showJumpToComment: !isEmpty(searchCommentText), onJumpToComment: onJumpToComment, downloadAttachment: downloadAttachment, ref: discussionIdFromUrl === discussion.item.id ? activeDiscussionElementRef : null })),
301
- discussion.discussionType === DiscussionType.CHAT && (React.createElement(CaseChat, { caseNumber: caseNumber, ref: discussionIdFromUrl === discussion.item.id ? activeDiscussionElementRef : null, chat: discussion.item, showJumpToComment: !isEmpty(searchCommentText), onJumpToComment: onJumpToComment })),
302
- discussion.discussionType === DiscussionType.EXTERNAL_TRACKER && (React.createElement(CaseExternalTrackerUpdate, { caseNumber: caseNumber, ref: discussionIdFromUrl === discussion.item.id ? activeDiscussionElementRef : null, trackerUpdate: discussion.item, showJumpToComment: !isEmpty(searchCommentText), onJumpToComment: onJumpToComment }))));
285
+ return (React.createElement("div", { "data-tracking-id": "discussion-elements", key: discussion.item.id }, discussion.discussionType === DiscussionType.COMMENT && (React.createElement(CaseComments, { caseNumber: caseNumber, ref: discussionIdFromUrl === discussion.item.id ? activeDiscussionElementRef : null, comment: discussion.item, onFeedBackUpdate: () => { }, feedback: discussion.feedback, accountNumber: props.accountNumber, onReply: onReply, showJumpToComment: !isEmpty(searchCommentText), onJumpToComment: onJumpToComment, downloadAttachment: () => { } }))));
303
286
  };
304
- const rowRenderer = ({ index, parent, key, style }) => {
287
+ const rowRenderer = ({ index, parent, key, style, }) => {
305
288
  const discussion = comuteSearchedComments[index];
306
289
  return (React.createElement(CellMeasurer, { key: key, cache: cellMeasureCache, parent: parent, columnIndex: 0, rowIndex: index },
307
290
  React.createElement("div", { className: "comment-virtualized-row", key: key, style: style }, renderCaseCommentElement(discussion))));
308
291
  };
292
+ /**
293
+ * One-shot scroll override used after search/filter changes.
294
+ * Resets the virtualized list to the top, then yields control back to
295
+ * WindowScroller's default onChildScroll for subsequent scroll events.
296
+ */
309
297
  const customChildScroll = ({ scrollTop }) => {
310
298
  var _a;
311
299
  if (!canUpdateScrollTop) {
@@ -318,23 +306,25 @@ export default function CaseDiscussion(props) {
318
306
  };
319
307
  const handleScroll = ({ scrollTop }) => {
320
308
  var _a, _b, _c, _d, _e, _f;
321
- // here we are doing couple of things
322
- // 1. check scroll position on the first comment, 2. set scrollposition as par window top
323
- // 3. if user click on jump to comment button then select row as per commentid and adding css class.
324
309
  isCommentSearchCleared && setIsCommentSearchCleared(false);
325
310
  if (listRef && commentRef) {
311
+ // Skip one scroll cycle after a search/filter reset to let customChildScroll re-sync
326
312
  if (!canUpdateScrollTop) {
327
313
  setCanUpdateScrollTop(true);
328
314
  return;
329
315
  }
330
316
  const commentTop = (_b = (_a = commentRef === null || commentRef === void 0 ? void 0 : commentRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) === null || _b === void 0 ? void 0 : _b.top;
317
+ // Re-apply search highlights on scroll since virtualized rows re-mount
331
318
  !isEmpty(searchCommentText) && highlightSearchResults(searchCommentText);
319
+ // Normal browsing or comment section visible above viewport — sync list position
332
320
  if (isEmpty(discussionIdFromUrl) || commentTop > 0) {
333
321
  (_c = listRef === null || listRef === void 0 ? void 0 : listRef.current) === null || _c === void 0 ? void 0 : _c.scrollToPosition(commentTop > 0 ? 0 : Math.abs(commentTop));
334
322
  }
335
323
  else if (commentTop < 0 && !isEmpty(discussionIdFromUrl) && !isJumpToCommentClicked) {
324
+ // Deep-link navigation: let WindowScroller drive the list scroll position
336
325
  (_d = listRef === null || listRef === void 0 ? void 0 : listRef.current) === null || _d === void 0 ? void 0 : _d.scrollToPosition(scrollTop);
337
326
  }
327
+ // After a "jump to comment" click, scroll the virtualized list to the target row
338
328
  if (isJumpToCommentClicked) {
339
329
  (_e = listRef === null || listRef === void 0 ? void 0 : listRef.current) === null || _e === void 0 ? void 0 : _e.scrollToRow(commentIndex);
340
330
  (_f = windowScrollerRef === null || windowScrollerRef === void 0 ? void 0 : windowScrollerRef.current) === null || _f === void 0 ? void 0 : _f.updatePosition();
@@ -342,7 +332,6 @@ export default function CaseDiscussion(props) {
342
332
  }
343
333
  }
344
334
  };
345
- const list = discussionFiltersListState.filter((filter) => filter.canSee);
346
335
  useEffect(() => {
347
336
  if (isPostingRemoteRequestCommentCompleted) {
348
337
  onCommentAdded();
@@ -350,59 +339,32 @@ export default function CaseDiscussion(props) {
350
339
  }
351
340
  // eslint-disable-next-line react-hooks/exhaustive-deps
352
341
  }, [isPostingRemoteRequestCommentCompleted]);
353
- const onToggleClick = (e) => {
354
- e.stopPropagation();
355
- setIsOpen(!isOpen);
356
- };
357
342
  const onSortToggleClick = (e) => {
358
343
  e.stopPropagation();
359
344
  setIsSortOpen(!isSortOpen);
360
345
  };
361
- const onFilterSelect = (_event, prevOption) => {
362
- const isFilterSelected = !prevOption.isSelected; // to get updated state of changed filter
363
- const prevSelectedFilters = list.filter((option) => option.isSelected && option.value !== prevOption.value); // to get previously selected filters
364
- const prevSelectedFiltersValue = prevSelectedFilters.map((option) => option.value);
365
- const newSelectedFilters = isFilterSelected // has all the selected values of filter with the updated selected filter state
366
- ? [...prevSelectedFiltersValue, prevOption.value]
367
- : prevSelectedFiltersValue;
368
- setFilter(dispatchDiscussion, newSelectedFilters, discussionFiltersListState);
369
- shouldRenderRVComponent && setCanUpdateScrollTop(false);
370
- setSelectedFilterItems(prevSelectedFiltersValue);
371
- };
372
- const toggle = (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: onToggleClick, isExpanded: isOpen, isFullWidth: true }, getDropdownBtnPlaceholder(t('Select a filter'), list.filter((i) => i.isSelected).map((i) => i.filterLabel), ' ', list.length, t('All comments'))));
373
- const onSortSelect = (_event, selectedSort) => {
374
- setSort(dispatchDiscussion, selectedSort, allDiscussions);
375
- setSortSelected(selectedSort.value);
346
+ const onSortSelect = (_event, value) => {
347
+ const selectedSort = discussionSortOrderConst.find((option) => option.name === value);
348
+ if (selectedSort) {
349
+ setSort(dispatchDiscussion, selectedSort, allDiscussions);
350
+ setSortSelected(selectedSort.name);
351
+ }
376
352
  setIsSortOpen(false);
377
353
  };
378
354
  const onSortToggle = (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: onSortToggleClick, isExpanded: isSortOpen, isFullWidth: true }, sort.name));
379
355
  return (React.createElement(React.Fragment, null,
380
356
  React.createElement(ErrorBoundary, { errorMsgInfo: { message: t('There was an error loading post comment section') } },
381
- React.createElement(AttachmentContextProvider, null,
382
- React.createElement(PostComment, { caseNumber: caseNumber, allDiscussions: allDiscussions, onCommentAdded: onCommentAdded, onAttachmentAdded: onAttachmentAdded, commentText: replyCommentText, onCommentClear: resetReplyComment, commentCreator: replyCommentCreator, defaultIsPublic: replyingToPrivateComment === null ? replyingToPrivateComment : !replyingToPrivateComment }))),
383
- React.createElement(LoadingIndicator, { className: "text-center", isInline: true, show: caseAttachmentHydra.isFetching ||
384
- caseExternalTrackersUpdate.isFetching ||
385
- caseFeedbacksHydra.isFetching ||
386
- (screenSessionRequested && allDiscussions.length === 0) }),
387
- !caseAttachmentHydra.isFetching &&
388
- !caseFeedbacksHydra.isFetching &&
389
- !caseExternalTrackersUpdate.isFetching &&
390
- !screenSessionRequested &&
391
- allDiscussions.length === 0 && React.createElement(Trans, null, "No comments"),
392
- !caseAttachmentHydra.isFetching &&
393
- !caseFeedbacksHydra.isFetching &&
394
- !caseExternalTrackersUpdate.isFetching &&
395
- allDiscussions.length !== 0 && (React.createElement(ErrorBoundary, { errorMsgInfo: { message: t('There was an error loading comments. Try refreshing.') } },
357
+ React.createElement(PostComment, { caseNumber: caseNumber, allDiscussions: allDiscussions, onCommentAdded: onCommentAdded, commentText: replyCommentText, onCommentClear: resetReplyComment, commentCreator: replyCommentCreator, defaultIsPublic: replyingToPrivateComment === null ? replyingToPrivateComment : !replyingToPrivateComment })),
358
+ props.commentsLoading && allDiscussions.length === 0 && React.createElement(LoadingIndicator, { size: "sm" }),
359
+ props.commentsError && allDiscussions.length === 0 && (React.createElement(AlertMessage, { variant: AlertType.DANGER, show: true, title: t('Failed to load comments. Try refreshing.') })),
360
+ !props.commentsLoading && !props.commentsError && allDiscussions.length === 0 && (React.createElement(Trans, null, "No comments")),
361
+ allDiscussions.length !== 0 && (React.createElement(ErrorBoundary, { errorMsgInfo: { message: t('There was an error loading comments. Try refreshing.') } },
396
362
  React.createElement("div", { className: `pfe-l-grid pfe-m-all-6-col pfe-m-gutters push-bottom ${isExportingPDF ? 'hide-in-pdf' : ''}` },
397
- React.createElement("div", { className: "pfe-m-12-col" }, allDiscussions.length > 20 && (React.createElement(CommentSearch, { caseDiscussions: computeFilteredComments || [], onCommentSearch: onCaseCommentSearch, isCommentSearchCleared: isCommentSearchCleared }))),
398
- React.createElement("span", { className: "push-top-narrow" },
399
- React.createElement("label", { htmlFor: "case-list-comments-dropdown", className: "pf-v6-u-mb-sm" }, t('Filter by')),
400
- React.createElement(Select, { "data-tracking-id": "discussions-filter-comments-dropdown", id: "case-list-comments-dropdown", role: "menu", isOpen: isOpen, selected: selectedFilterItems, onSelect: onFilterSelect, onOpenChange: (isOpen) => setIsOpen(isOpen), toggle: toggle, popperProps: { direction: 'down', enableFlip: false }, zIndex: 9 },
401
- React.createElement(SelectList, null, list.map((option) => (React.createElement(SelectOption, { hasCheckbox: true, value: option, isSelected: option.isSelected, isDisabled: option.isDisabled }, option.filterLabel)))))),
363
+ React.createElement("div", { className: "pfe-m-12-col" }, allDiscussions.length > 20 && (React.createElement(CommentSearch, { caseDiscussions: allDiscussions || [], onCommentSearch: onCaseCommentSearch, isCommentSearchCleared: isCommentSearchCleared }))),
402
364
  React.createElement("span", { className: "push-top-narrow" },
403
365
  React.createElement("label", { htmlFor: "se-discussion-sort", className: "pf-v6-u-mb-sm" }, t('Sort by')),
404
366
  React.createElement(Select, { id: "se-discussion-sort", "data-tracking-id": "discussions-sort-dropdown", isOpen: isSortOpen, selected: sortSelected, onSelect: onSortSelect, onOpenChange: (isSortOpen) => setIsSortOpen(isSortOpen), toggle: onSortToggle, shouldFocusToggleOnSelect: true, popperProps: { direction: 'down', enableFlip: false }, zIndex: 9 },
405
- React.createElement(SelectList, null, discussionSortOrderConst.map((option) => (React.createElement(SelectOption, { value: option, isSelected: isEqual(sort.name, option.name) }, option.name))))))),
367
+ React.createElement(SelectList, null, discussionSortOrderConst.map((option) => (React.createElement(SelectOption, { value: option.name, isSelected: isEqual(sort.name, option.name) }, option.name))))))),
406
368
  React.createElement("div", { className: "support-comment-section" }, !shouldRenderRVComponent &&
407
369
  !isSearchResultEmpty &&
408
370
  comuteSearchedComments.map((discussion, index) => {
@@ -0,0 +1,15 @@
1
+ import { Dispatch, MutableRefObject } from 'react';
2
+ interface UseAttachmentUploadCompletionParams {
3
+ isUploadingAttachments: boolean;
4
+ hasSomeUnUploadedFiles: boolean;
5
+ isUploadingAttachmentPrevious: MutableRefObject<boolean>;
6
+ caseDispatch: Dispatch<any>;
7
+ isInternal: boolean;
8
+ status: string | undefined;
9
+ resetAttachmentSection: () => void;
10
+ onAttachmentAdded: () => void;
11
+ setCssCommentAgreed: (v: boolean) => void;
12
+ }
13
+ export declare function useAttachmentUploadCompletion({ isUploadingAttachments, hasSomeUnUploadedFiles, isUploadingAttachmentPrevious, caseDispatch, isInternal, status, resetAttachmentSection, onAttachmentAdded, setCssCommentAgreed, }: UseAttachmentUploadCompletionParams): void;
14
+ export {};
15
+ //# sourceMappingURL=useAttachmentUploadCompletion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAttachmentUploadCompletion.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useAttachmentUploadCompletion.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAa,MAAM,OAAO,CAAC;AAK9D,UAAU,mCAAmC;IACzC,sBAAsB,EAAE,OAAO,CAAC;IAChC,sBAAsB,EAAE,OAAO,CAAC;IAChC,6BAA6B,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACzD,YAAY,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,sBAAsB,EAAE,MAAM,IAAI,CAAC;IACnC,iBAAiB,EAAE,MAAM,IAAI,CAAC;IAC9B,mBAAmB,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CAC7C;AAED,wBAAgB,6BAA6B,CAAC,EAC1C,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,YAAY,EACZ,UAAU,EACV,MAAM,EACN,sBAAsB,EACtB,iBAAiB,EACjB,mBAAmB,GACtB,EAAE,mCAAmC,QAiBrC"}
@@ -0,0 +1,19 @@
1
+ import { ToastNotification } from '@rh-support/components';
2
+ import { useEffect } from 'react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { checkForCaseStatusToggleOnAttachOrComment } from '../../../../../reducers/CaseReducer';
5
+ export function useAttachmentUploadCompletion({ isUploadingAttachments, hasSomeUnUploadedFiles, isUploadingAttachmentPrevious, caseDispatch, isInternal, status, resetAttachmentSection, onAttachmentAdded, setCssCommentAgreed, }) {
6
+ const { t } = useTranslation();
7
+ useEffect(() => {
8
+ const justFinished = isUploadingAttachmentPrevious.current && !isUploadingAttachments && !hasSomeUnUploadedFiles;
9
+ if (justFinished) {
10
+ ToastNotification.addSuccessMessage(t('Attachment(s) uploaded successfully'));
11
+ checkForCaseStatusToggleOnAttachOrComment(caseDispatch, isInternal, status);
12
+ resetAttachmentSection();
13
+ onAttachmentAdded();
14
+ setCssCommentAgreed(false);
15
+ }
16
+ isUploadingAttachmentPrevious.current = isUploadingAttachments;
17
+ // eslint-disable-next-line react-hooks/exhaustive-deps
18
+ }, [isUploadingAttachments]);
19
+ }
@@ -0,0 +1,17 @@
1
+ import { EditorMode } from '@rh-support/components';
2
+ import { Dispatch, SetStateAction } from 'react';
3
+ interface UseCommentDraftsParams {
4
+ caseNumber: string;
5
+ loggedInUserSSO: string;
6
+ commentSignature: string;
7
+ setCommentText: Dispatch<SetStateAction<string>>;
8
+ setCommentType: Dispatch<SetStateAction<EditorMode>>;
9
+ setIsPublic: Dispatch<SetStateAction<boolean | undefined>>;
10
+ applySignature: (text: string) => void;
11
+ }
12
+ export declare function useCommentDrafts({ caseNumber, loggedInUserSSO, commentSignature, setCommentText, setCommentType, setIsPublic, applySignature, }: UseCommentDraftsParams): {
13
+ saveDraft: (...args: any[]) => void;
14
+ clearDraft: () => Promise<void>;
15
+ };
16
+ export {};
17
+ //# sourceMappingURL=useCommentDrafts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCommentDrafts.d.ts","sourceRoot":"","sources":["../../../../../../../src/components/CaseEditView/Tabs/CaseDiscussion/PostComment/useCommentDrafts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAsB,MAAM,OAAO,CAAC;AAMrE,UAAU,sBAAsB;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IACjD,cAAc,EAAE,QAAQ,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC;IACrD,WAAW,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC;IAC3D,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAED,wBAAgB,gBAAgB,CAAC,EAC7B,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,WAAW,EACX,cAAc,GACjB,EAAE,sBAAsB;;;EAgDxB"}
@@ -0,0 +1,49 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { debounce } from '@patternfly/react-core';
11
+ import { useEffect, useMemo } from 'react';
12
+ import { CaseDetailsCacheNamespaces } from '../../../../../enums/cache';
13
+ import { CaseDetailsCacheUtils } from '../../../../../utils/caseDetailsCacheUtils';
14
+ export function useCommentDrafts({ caseNumber, loggedInUserSSO, commentSignature, setCommentText, setCommentType, setIsPublic, applySignature, }) {
15
+ const saveDraft = useMemo(() => debounce(function (value, mode, publicFlag) {
16
+ return __awaiter(this, void 0, void 0, function* () {
17
+ const { value: prevDraftComments = {} } = (yield CaseDetailsCacheUtils.get(CaseDetailsCacheNamespaces.COMMENTS_DRAFT.name, loggedInUserSSO)) || {};
18
+ prevDraftComments[caseNumber] = { text: value, privateFlag: !publicFlag, type: mode };
19
+ CaseDetailsCacheUtils.set(CaseDetailsCacheNamespaces.COMMENTS_DRAFT.name, loggedInUserSSO, {
20
+ value: prevDraftComments,
21
+ });
22
+ });
23
+ }, 500), [caseNumber, loggedInUserSSO]);
24
+ const clearDraft = () => __awaiter(this, void 0, void 0, function* () {
25
+ const { value: prevDraftComments = {} } = (yield CaseDetailsCacheUtils.get(CaseDetailsCacheNamespaces.COMMENTS_DRAFT.name, loggedInUserSSO)) || {};
26
+ delete prevDraftComments[caseNumber];
27
+ CaseDetailsCacheUtils.set(CaseDetailsCacheNamespaces.COMMENTS_DRAFT.name, loggedInUserSSO, {
28
+ value: prevDraftComments,
29
+ });
30
+ });
31
+ useEffect(() => {
32
+ function loadDraft() {
33
+ return __awaiter(this, void 0, void 0, function* () {
34
+ var _a;
35
+ const { value: draftComments = {} } = (yield CaseDetailsCacheUtils.get(CaseDetailsCacheNamespaces.COMMENTS_DRAFT.name, loggedInUserSSO)) || {};
36
+ const draft = draftComments[caseNumber];
37
+ if (!((_a = draft === null || draft === void 0 ? void 0 : draft.text) === null || _a === void 0 ? void 0 : _a.trim()))
38
+ return;
39
+ applySignature(draft.text);
40
+ if (draft.privateFlag)
41
+ setIsPublic(false);
42
+ setCommentType(draft.type);
43
+ });
44
+ }
45
+ loadDraft();
46
+ // eslint-disable-next-line react-hooks/exhaustive-deps
47
+ }, [commentSignature]);
48
+ return { saveDraft, clearDraft };
49
+ }
@@ -5,9 +5,13 @@ interface IProps {
5
5
  commentText?: string;
6
6
  commentCreator?: string;
7
7
  onCommentAdded?: () => Promise<void>;
8
- onAttachmentAdded?: () => void;
9
- onAttachmentRemove?: (uuid: string) => void;
10
8
  onCommentClear?: () => void;
9
+ /**
10
+ * Controls the initial visibility toggle state.
11
+ * - `true` / `false` — sets comment visibility to public/private.
12
+ * - `null` — resets to the default (public).
13
+ * - `undefined` — same as omitting the prop.
14
+ */
11
15
  defaultIsPublic?: boolean;
12
16
  allDiscussions: IDiscussion[];
13
17
  }