@redacto.io/consent-sdk-react 3.5.0 → 3.6.0

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 (152) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +176 -0
  3. package/dist/index.d.mts +51 -4
  4. package/dist/index.d.ts +51 -4
  5. package/dist/index.js +1891 -243
  6. package/dist/index.mjs +1890 -243
  7. package/dist/privacy-center.d.mts +566 -0
  8. package/dist/privacy-center.d.ts +566 -0
  9. package/dist/privacy-center.js +17174 -0
  10. package/dist/privacy-center.mjs +17136 -0
  11. package/package.json +47 -3
  12. package/src/RedactoNoticeConsent/RedactoNoticeConsent.test.tsx +201 -118
  13. package/src/RedactoNoticeConsent/RedactoNoticeConsent.tsx +1194 -93
  14. package/src/RedactoNoticeConsent/api/index.ts +335 -206
  15. package/src/RedactoNoticeConsent/api/types.ts +140 -16
  16. package/src/RedactoNoticeConsent/components/DigiLockerCallback.tsx +602 -0
  17. package/src/RedactoNoticeConsent/components/index.ts +7 -0
  18. package/src/RedactoNoticeConsent/injectStyles.ts +11 -0
  19. package/src/RedactoNoticeConsent/language-codes.ts +61 -0
  20. package/src/RedactoNoticeConsent/services/storage.ts +122 -0
  21. package/src/RedactoNoticeConsent/types.ts +2 -0
  22. package/src/RedactoPrivacyCenter/PrivacyCenterActivityPage.tsx +19 -0
  23. package/src/RedactoPrivacyCenter/PrivacyCenterConsentManagerPage.tsx +17 -0
  24. package/src/RedactoPrivacyCenter/PrivacyCenterFormPage.tsx +79 -0
  25. package/src/RedactoPrivacyCenter/PrivacyCenterHomePage.tsx +27 -0
  26. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +142 -0
  27. package/src/RedactoPrivacyCenter/RedactoPrivacyCenter.tsx +51 -0
  28. package/src/RedactoPrivacyCenter/api/actions.ts +444 -0
  29. package/src/RedactoPrivacyCenter/api/client.ts +413 -0
  30. package/src/RedactoPrivacyCenter/api/config.ts +33 -0
  31. package/src/RedactoPrivacyCenter/api/fetcher.ts +181 -0
  32. package/src/RedactoPrivacyCenter/api/upload.ts +93 -0
  33. package/src/RedactoPrivacyCenter/assets/character.svg +31 -0
  34. package/src/RedactoPrivacyCenter/assets/fileAttach.svg +9 -0
  35. package/src/RedactoPrivacyCenter/assets/greenTick.svg +4 -0
  36. package/src/RedactoPrivacyCenter/assets/mail.svg +3 -0
  37. package/src/RedactoPrivacyCenter/assets/poweredByRedacto.svg +9 -0
  38. package/src/RedactoPrivacyCenter/assets/sentImg.svg +9 -0
  39. package/src/RedactoPrivacyCenter/assets/uploadFileIcon.svg +9 -0
  40. package/src/RedactoPrivacyCenter/components/ActivitiesList.tsx +272 -0
  41. package/src/RedactoPrivacyCenter/components/CaseDetailsModal.tsx +468 -0
  42. package/src/RedactoPrivacyCenter/components/CaseHistory.tsx +433 -0
  43. package/src/RedactoPrivacyCenter/components/ConsentManager.tsx +726 -0
  44. package/src/RedactoPrivacyCenter/components/ConsentManagerColumns.tsx +239 -0
  45. package/src/RedactoPrivacyCenter/components/ConsentManagerConstants.ts +7 -0
  46. package/src/RedactoPrivacyCenter/components/ConsentManagerTableSkeleton.tsx +155 -0
  47. package/src/RedactoPrivacyCenter/components/DataRequest/CorrectionDataSection.tsx +189 -0
  48. package/src/RedactoPrivacyCenter/components/DataRequest/GrievanceDataSection.tsx +118 -0
  49. package/src/RedactoPrivacyCenter/components/DataRequest/NominationDataSection.tsx +66 -0
  50. package/src/RedactoPrivacyCenter/components/DataRequest/PurposeSelectionSection.tsx +305 -0
  51. package/src/RedactoPrivacyCenter/components/Form.tsx +1455 -0
  52. package/src/RedactoPrivacyCenter/components/GrievanceTable.tsx +176 -0
  53. package/src/RedactoPrivacyCenter/components/GrievanceTablePagination.tsx +330 -0
  54. package/src/RedactoPrivacyCenter/components/ModifyConsentModal.tsx +329 -0
  55. package/src/RedactoPrivacyCenter/components/Pagination.tsx +140 -0
  56. package/src/RedactoPrivacyCenter/components/PrivacyCenterLayout.tsx +302 -0
  57. package/src/RedactoPrivacyCenter/components/RequestSubmit.tsx +218 -0
  58. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideBarPC.tsx +475 -0
  59. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/SideMenuPC.tsx +412 -0
  60. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/constants.tsx +42 -0
  61. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/menuItemsPC.tsx +1 -0
  62. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/types.ts +40 -0
  63. package/src/RedactoPrivacyCenter/components/SideMenuBarPC/utils.ts +38 -0
  64. package/src/RedactoPrivacyCenter/components/ThemeTransitionOverlay.tsx +85 -0
  65. package/src/RedactoPrivacyCenter/components/activitiesColumns.tsx +141 -0
  66. package/src/RedactoPrivacyCenter/components/caseHistoryColumns.tsx +179 -0
  67. package/src/RedactoPrivacyCenter/components/constant.ts +20 -0
  68. package/src/RedactoPrivacyCenter/components/types.ts +68 -0
  69. package/src/RedactoPrivacyCenter/components/utils.tsx +10 -0
  70. package/src/RedactoPrivacyCenter/config/routes.ts +9 -0
  71. package/src/RedactoPrivacyCenter/config/table.ts +25 -0
  72. package/src/RedactoPrivacyCenter/context/AuthContext.tsx +123 -0
  73. package/src/RedactoPrivacyCenter/context/BaseUrlContext.tsx +44 -0
  74. package/src/RedactoPrivacyCenter/context/LocaleContext.tsx +11 -0
  75. package/src/RedactoPrivacyCenter/context/NavigationContext.tsx +64 -0
  76. package/src/RedactoPrivacyCenter/context/OrganizationContext.tsx +30 -0
  77. package/src/RedactoPrivacyCenter/context/PageChromeContext.tsx +60 -0
  78. package/src/RedactoPrivacyCenter/context/theme.tsx +127 -0
  79. package/src/RedactoPrivacyCenter/lib/api.ts +468 -0
  80. package/src/RedactoPrivacyCenter/lib/consent-manager/utils.tsx +63 -0
  81. package/src/RedactoPrivacyCenter/lib/constants.ts +116 -0
  82. package/src/RedactoPrivacyCenter/lib/i18n.ts +81 -0
  83. package/src/RedactoPrivacyCenter/lib/locales.ts +105 -0
  84. package/src/RedactoPrivacyCenter/lib/queries.ts +30 -0
  85. package/src/RedactoPrivacyCenter/lib/types.ts +346 -0
  86. package/src/RedactoPrivacyCenter/lib/useCurrentLanguage.ts +37 -0
  87. package/src/RedactoPrivacyCenter/lib/utils.ts +625 -0
  88. package/src/RedactoPrivacyCenter/locales/as/translation.json +188 -0
  89. package/src/RedactoPrivacyCenter/locales/bho/translation.json +188 -0
  90. package/src/RedactoPrivacyCenter/locales/bn/translation.json +188 -0
  91. package/src/RedactoPrivacyCenter/locales/brx/translation.json +188 -0
  92. package/src/RedactoPrivacyCenter/locales/doi/translation.json +188 -0
  93. package/src/RedactoPrivacyCenter/locales/en/translation.json +188 -0
  94. package/src/RedactoPrivacyCenter/locales/gom/translation.json +188 -0
  95. package/src/RedactoPrivacyCenter/locales/gu/translation.json +188 -0
  96. package/src/RedactoPrivacyCenter/locales/hi/translation.json +188 -0
  97. package/src/RedactoPrivacyCenter/locales/kn/translation.json +188 -0
  98. package/src/RedactoPrivacyCenter/locales/ks/translation.json +188 -0
  99. package/src/RedactoPrivacyCenter/locales/mai/translation.json +188 -0
  100. package/src/RedactoPrivacyCenter/locales/ml/translation.json +188 -0
  101. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +188 -0
  102. package/src/RedactoPrivacyCenter/locales/mr/translation.json +188 -0
  103. package/src/RedactoPrivacyCenter/locales/ne/translation.json +188 -0
  104. package/src/RedactoPrivacyCenter/locales/or/translation.json +188 -0
  105. package/src/RedactoPrivacyCenter/locales/pa/translation.json +188 -0
  106. package/src/RedactoPrivacyCenter/locales/sa/translation.json +188 -0
  107. package/src/RedactoPrivacyCenter/locales/sat/translation.json +188 -0
  108. package/src/RedactoPrivacyCenter/locales/sd/translation.json +188 -0
  109. package/src/RedactoPrivacyCenter/locales/ta/translation.json +188 -0
  110. package/src/RedactoPrivacyCenter/locales/te/translation.json +188 -0
  111. package/src/RedactoPrivacyCenter/locales/ur/translation.json +188 -0
  112. package/src/RedactoPrivacyCenter/styles/base.css +117 -0
  113. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1000 -0
  114. package/src/RedactoPrivacyCenter/styles/pcStyles.ts +819 -0
  115. package/src/RedactoPrivacyCenter/styles/responsive.ts +138 -0
  116. package/src/RedactoPrivacyCenter/styles/variables.css +133 -0
  117. package/src/RedactoPrivacyCenter/table/AdvancedTable.tsx +365 -0
  118. package/src/RedactoPrivacyCenter/table/AdvancedTablePagination.tsx +479 -0
  119. package/src/RedactoPrivacyCenter/table/index.ts +2 -0
  120. package/src/RedactoPrivacyCenter/types/images.d.ts +29 -0
  121. package/src/RedactoPrivacyCenter/ui/Footer.tsx +73 -0
  122. package/src/RedactoPrivacyCenter/ui/FormBlockWrapper.tsx +54 -0
  123. package/src/RedactoPrivacyCenter/ui/PCAccordion.tsx +87 -0
  124. package/src/RedactoPrivacyCenter/ui/PCActionIcon.tsx +47 -0
  125. package/src/RedactoPrivacyCenter/ui/PCAvatar.tsx +69 -0
  126. package/src/RedactoPrivacyCenter/ui/PCBadge.tsx +48 -0
  127. package/src/RedactoPrivacyCenter/ui/PCButton.tsx +77 -0
  128. package/src/RedactoPrivacyCenter/ui/PCCheckbox.tsx +55 -0
  129. package/src/RedactoPrivacyCenter/ui/PCCollapse.tsx +97 -0
  130. package/src/RedactoPrivacyCenter/ui/PCEmpty.tsx +35 -0
  131. package/src/RedactoPrivacyCenter/ui/PCInput.tsx +128 -0
  132. package/src/RedactoPrivacyCenter/ui/PCLoader.tsx +30 -0
  133. package/src/RedactoPrivacyCenter/ui/PCModal.tsx +104 -0
  134. package/src/RedactoPrivacyCenter/ui/PCPopover.tsx +175 -0
  135. package/src/RedactoPrivacyCenter/ui/PCSelect.tsx +381 -0
  136. package/src/RedactoPrivacyCenter/ui/PCTable.tsx +137 -0
  137. package/src/RedactoPrivacyCenter/ui/PCText.tsx +39 -0
  138. package/src/RedactoPrivacyCenter/ui/PCTextarea.tsx +43 -0
  139. package/src/RedactoPrivacyCenter/ui/PCTitle.tsx +37 -0
  140. package/src/RedactoPrivacyCenter/ui/PCTooltip.tsx +119 -0
  141. package/src/RedactoPrivacyCenter/ui/SimpleBar.tsx +25 -0
  142. package/src/RedactoPrivacyCenter/ui/StatusBadge.tsx +55 -0
  143. package/src/RedactoPrivacyCenter/ui/ThemeToggle.tsx +70 -0
  144. package/src/RedactoPrivacyCenter/ui/index.ts +18 -0
  145. package/src/RedactoPrivacyCenter/utils/cn.ts +28 -0
  146. package/src/RedactoPrivacyCenter/utils/date.ts +48 -0
  147. package/src/RedactoPrivacyCenter/utils/status.ts +111 -0
  148. package/src/RedactoPrivacyCenter/utils/toast.ts +130 -0
  149. package/src/index.ts +10 -0
  150. package/src/privacy-center.ts +107 -0
  151. package/tsconfig.json +2 -0
  152. package/tsup.config.ts +16 -1
@@ -0,0 +1,433 @@
1
+ import { useState, useMemo, useCallback, useEffect } from 'react';
2
+ import { useTranslation } from 'react-i18next';
3
+ import {
4
+ PCTitle,
5
+ PCText,
6
+ PCButton,
7
+ PCInput,
8
+ PCLoader,
9
+ } from '../ui';
10
+ import { pcStyles } from '../styles/pcStyles';
11
+ import { FiSearch, FiFilter } from 'react-icons/fi';
12
+ import { IoAdd } from 'react-icons/io5';
13
+ import type { CaseRequest } from '../lib/types';
14
+ import { RequestTypeEnum } from '../lib/types';
15
+ import { useCaseHistory } from '../lib/queries';
16
+ import { useOrganization } from '../context/OrganizationContext';
17
+ import { useAuth } from '../context/AuthContext';
18
+ import { useRequestTypeLabels, useGrievanceTabs } from '../lib/constants';
19
+ import type { CaseHistoryProps } from './types';
20
+ import { GRIEVANCE_TAB_IDS } from './constant';
21
+ import CaseDetailsModal from './CaseDetailsModal';
22
+ import { getCoreRowModel, useReactTable } from '@tanstack/react-table';
23
+ import { useCaseHistoryColumns } from './caseHistoryColumns';
24
+ import { PCPopover, PCCheckbox } from '../ui';
25
+ import GrievanceTable from './GrievanceTable';
26
+ import GrievanceTablePagination from './GrievanceTablePagination';
27
+
28
+ export default function CaseHistory({ onCreateRequest }: CaseHistoryProps) {
29
+ const { t } = useTranslation();
30
+ const grievanceTabs = useGrievanceTabs();
31
+ const requestTypeLabels = useRequestTypeLabels();
32
+ const [activeTab, setActiveTab] = useState<string>(GRIEVANCE_TAB_IDS.ALL);
33
+ const [searchQuery, setSearchQuery] = useState('');
34
+ const [selectedRequestTypes, setSelectedRequestTypes] = useState<string[]>(
35
+ Object.values(RequestTypeEnum)
36
+ );
37
+ const [selectedCase, setSelectedCase] = useState<CaseRequest | null>(null);
38
+ const [isModalOpen, setIsModalOpen] = useState(false);
39
+ const [pageIndex, setPageIndex] = useState(0);
40
+ const [pageSize, setPageSize] = useState(10);
41
+ const { organization } = useOrganization();
42
+ const { accessToken, refreshToken, onError, updateTokens } = useAuth();
43
+ const organizationId = organization?.uuid;
44
+ const workspaceId = organization?.workspaces[0]?.uuid;
45
+ const contact =
46
+ typeof window !== 'undefined' ? sessionStorage.getItem('email') || '' : '';
47
+
48
+ // Create auth context object for API calls - memoized to prevent infinite loops
49
+ const authContext = useMemo(() => ({
50
+ accessToken,
51
+ refreshToken,
52
+ onError,
53
+ updateTokens,
54
+ }), [accessToken, refreshToken, onError, updateTokens]);
55
+
56
+ const {
57
+ data: cases,
58
+ isLoading,
59
+ error,
60
+ isError,
61
+ } = useCaseHistory(organizationId, workspaceId, contact, authContext);
62
+
63
+ const filteredCases = useMemo(() => {
64
+ return (cases || []).filter((item: CaseRequest) => {
65
+ const statusLower = item.status.toLowerCase();
66
+
67
+ // Filter by tab (status filter)
68
+ if (activeTab !== GRIEVANCE_TAB_IDS.ALL) {
69
+ if (
70
+ activeTab === GRIEVANCE_TAB_IDS.PROCESSING &&
71
+ !['processing', 'in_progress', 'initiated', 'assigned'].includes(
72
+ statusLower
73
+ )
74
+ ) {
75
+ return false;
76
+ }
77
+ if (activeTab === GRIEVANCE_TAB_IDS.COMPLETED && statusLower !== 'completed') {
78
+ return false;
79
+ }
80
+ if (
81
+ activeTab === GRIEVANCE_TAB_IDS.REJECTED &&
82
+ !['rejected', 'declined'].includes(statusLower)
83
+ ) {
84
+ return false;
85
+ }
86
+ }
87
+
88
+ // Filter by request type
89
+ // Only apply filter if some types are selected (not all)
90
+ const allTypes = Object.values(RequestTypeEnum);
91
+ const hasAllTypesSelected = selectedRequestTypes.length === allTypes.length &&
92
+ allTypes.every(type => selectedRequestTypes.includes(type));
93
+
94
+ if (!hasAllTypesSelected && selectedRequestTypes.length > 0) {
95
+ const itemRequestType = item.request_type?.toUpperCase();
96
+ if (!itemRequestType || !selectedRequestTypes.some(rt => rt.toUpperCase() === itemRequestType)) {
97
+ return false;
98
+ }
99
+ }
100
+
101
+ // Filter by search
102
+ if (searchQuery) {
103
+ const query = searchQuery.toLowerCase();
104
+ const matchesId = item.case_id?.toLowerCase().includes(query);
105
+ const matchesDesc =
106
+ item.description?.toLowerCase().includes(query) ||
107
+ item.request_details?.purposes?.[0]?.purpose?.description
108
+ ?.toLowerCase()
109
+ .includes(query);
110
+
111
+ if (!matchesId && !matchesDesc) return false;
112
+ }
113
+ return true;
114
+ });
115
+ }, [cases, activeTab, selectedRequestTypes, searchQuery]);
116
+
117
+ const handleViewDetails = useCallback((caseRequest: CaseRequest) => {
118
+ setSelectedCase(caseRequest);
119
+ setIsModalOpen(true);
120
+ }, []);
121
+
122
+ const columns = useCaseHistoryColumns({
123
+ onViewDetails: handleViewDetails,
124
+ });
125
+
126
+ // Reset to first page when filters change (but not when pagination changes)
127
+ useEffect(() => {
128
+ setPageIndex(0);
129
+ }, [activeTab, searchQuery, selectedRequestTypes]);
130
+
131
+ const table = useReactTable<CaseRequest>({
132
+ data: filteredCases,
133
+ columns: columns,
134
+ getCoreRowModel: getCoreRowModel(),
135
+ manualPagination: false, // Client-side pagination - we'll handle it manually
136
+ getRowId: (row) => row.uuid || row.case_id,
137
+ state: {
138
+ pagination: {
139
+ pageIndex: pageIndex,
140
+ pageSize: pageSize,
141
+ },
142
+ },
143
+ onPaginationChange: (updater) => {
144
+ const currentState = { pageIndex, pageSize };
145
+ const next =
146
+ typeof updater === 'function'
147
+ ? updater(currentState)
148
+ : updater;
149
+ const nextPageIndex = (next as any).pageIndex;
150
+ const nextPageSize = (next as any).pageSize;
151
+
152
+ // Update state directly
153
+ if (typeof nextPageIndex === 'number') {
154
+ setPageIndex(nextPageIndex);
155
+ }
156
+ if (typeof nextPageSize === 'number') {
157
+ setPageSize(nextPageSize);
158
+ // Reset to first page when page size changes
159
+ setPageIndex(0);
160
+ }
161
+ },
162
+ meta: {
163
+ onRowClick: handleViewDetails,
164
+ },
165
+ });
166
+
167
+ if (isLoading) {
168
+ return (
169
+ <div style={{
170
+ ...pcStyles.flex,
171
+ height: '24rem',
172
+ width: '100%',
173
+ ...pcStyles.itemsCenter,
174
+ ...pcStyles.justifyCenter,
175
+ }}>
176
+ <PCLoader variant="spinner" size="lg" />
177
+ </div>
178
+ );
179
+ }
180
+
181
+ if (isError) {
182
+ return (
183
+ <div style={{
184
+ ...pcStyles.flex,
185
+ ...pcStyles.flexCol,
186
+ height: '24rem',
187
+ width: '100%',
188
+ ...pcStyles.itemsCenter,
189
+ ...pcStyles.justifyCenter,
190
+ }}>
191
+ <PCText style={{ marginBottom: pcStyles.mbMd.marginBottom, color: '#dc2626' }}>
192
+ {t('errorLoadingCaseHistory')}
193
+ </PCText>
194
+ <PCText size="sm" color="secondary">
195
+ {error?.message || t('unknownError')}
196
+ </PCText>
197
+ </div>
198
+ );
199
+ }
200
+
201
+ if (!contact) {
202
+ return (
203
+ <div style={{
204
+ ...pcStyles.flex,
205
+ ...pcStyles.flexCol,
206
+ height: '24rem',
207
+ width: '100%',
208
+ ...pcStyles.itemsCenter,
209
+ ...pcStyles.justifyCenter,
210
+ }}>
211
+ <PCText style={{ marginBottom: pcStyles.mbMd.marginBottom, color: '#6b7280' }}>
212
+ {t('pleaseSignInToViewGrievance')}
213
+ </PCText>
214
+ <PCText size="sm" color="secondary">
215
+ {t('emailNotFoundInSession')}
216
+ </PCText>
217
+ </div>
218
+ );
219
+ }
220
+
221
+ const getVar = (varName: string): string => `var(${varName})`;
222
+
223
+ return (
224
+ <div style={{ width: '100%' }}>
225
+ {/* Top Actions */}
226
+ <div style={{
227
+ display: 'flex',
228
+ justifyContent: 'flex-end',
229
+ alignItems: 'center',
230
+ paddingTop: '0.5rem',
231
+ marginBottom: getVar('--pc-spacing-md'),
232
+ width: '100%',
233
+ }}>
234
+ <div style={{
235
+ display: 'flex',
236
+ width: '100%',
237
+ justifyContent: 'flex-end',
238
+ }} className="pc-export-button-responsive">
239
+ <PCButton
240
+ size="sm"
241
+ onClick={onCreateRequest}
242
+ style={{
243
+ whiteSpace: 'nowrap',
244
+ width: '100%',
245
+ ...pcStyles.gapSm,
246
+ }}
247
+ >
248
+ <IoAdd size={15} /> <span>{t('newRequest')}</span>
249
+ </PCButton>
250
+ </div>
251
+ </div>
252
+
253
+ {/* Filters Section: Search on left, Tabs and Filter on rightmost - Responsive */}
254
+ <div style={{
255
+ display: 'flex',
256
+ flexDirection: 'column',
257
+ gap: getVar('--pc-spacing-md'),
258
+ marginBottom: getVar('--pc-spacing-lg'),
259
+ width: '100%',
260
+ }} className="pc-filters-responsive">
261
+ {/* Search Input - Full width on mobile, constrained on desktop */}
262
+ <div style={{
263
+ width: '100%',
264
+ }} className="pc-search-responsive">
265
+ <PCInput
266
+ type="search"
267
+ clearable
268
+ placeholder={t('searchCasesByID')}
269
+ value={searchQuery}
270
+ onClear={() => setSearchQuery('')}
271
+ prefix={<FiSearch size={20} strokeWidth={1.2} />}
272
+ onChange={(e) => {
273
+ setSearchQuery(e.target.value);
274
+ }}
275
+ style={{
276
+ width: '100%',
277
+ }}
278
+ />
279
+ </div>
280
+
281
+ {/* Tabs and Filter Dropdown - Rightmost, Stacked on mobile, horizontal on desktop */}
282
+ <div style={{
283
+ display: 'flex',
284
+ flexDirection: 'column',
285
+ gap: getVar('--pc-spacing-sm'),
286
+ }} className="pc-filters-row-responsive">
287
+ {/* Tabs */}
288
+ <div style={{
289
+ ...pcStyles.overflowXAuto,
290
+ flexShrink: 1,
291
+ minWidth: 0,
292
+ }}>
293
+ <div style={{
294
+ display: 'inline-flex',
295
+ minWidth: 'max-content',
296
+ ...pcStyles.itemsCenter,
297
+ gap: '0.25rem',
298
+ borderRadius: '0.5rem',
299
+ backgroundColor: getVar('--pc-bg-secondary'),
300
+ padding: '0.375rem',
301
+ border: `1px solid ${getVar('--pc-border-color')}`,
302
+ }}>
303
+ {grievanceTabs.map((tab) => (
304
+ <button
305
+ key={tab.id}
306
+ style={{
307
+ display: 'inline-flex',
308
+ ...pcStyles.itemsCenter,
309
+ gap: '0.5rem',
310
+ borderRadius: '0.375rem',
311
+ padding: '0.375rem 0.75rem',
312
+ fontSize: pcStyles.textXs.fontSize,
313
+ fontWeight: pcStyles.fontWeightSemibold.fontWeight,
314
+ transition: 'all 0.2s',
315
+ backgroundColor: activeTab === tab.id ? getVar('--pc-primary') : 'transparent',
316
+ color: activeTab === tab.id ? '#ffffff' : getVar('--pc-text-secondary'),
317
+ border: 'none',
318
+ cursor: 'pointer',
319
+ whiteSpace: 'nowrap',
320
+ }}
321
+ onClick={() => {
322
+ setActiveTab(tab.id);
323
+ }}
324
+ >
325
+ {tab.label}
326
+ </button>
327
+ ))}
328
+ </div>
329
+ </div>
330
+
331
+ {/* Filter Dropdown */}
332
+ <div style={{
333
+ flexShrink: 0,
334
+ display: 'flex',
335
+ }} className="pc-filter-button-wrapper">
336
+ <PCPopover
337
+ shadow="lg"
338
+ placement="bottom-end"
339
+ content={
340
+ <div style={{
341
+ minWidth: '160px',
342
+ padding: '0.75rem',
343
+ maxHeight: 'none',
344
+ overflow: 'visible'
345
+ }}>
346
+ <div style={{
347
+ ...pcStyles.flex,
348
+ ...pcStyles.flexCol,
349
+ gap: '0.5rem'
350
+ }}>
351
+ <PCTitle
352
+ as="h6"
353
+ style={{
354
+ marginBottom: '0.5rem',
355
+ paddingLeft: '0.125rem',
356
+ fontSize: pcStyles.textSm.fontSize,
357
+ fontWeight: pcStyles.fontWeightSemibold.fontWeight
358
+ }}
359
+ >
360
+ {t('filterByType')}
361
+ </PCTitle>
362
+ {Object.values(RequestTypeEnum).map((type) => (
363
+ <div
364
+ key={type}
365
+ style={{
366
+ ...pcStyles.flex,
367
+ ...pcStyles.itemsCenter,
368
+ padding: '0.375rem 0.25rem',
369
+ minHeight: '2rem'
370
+ }}
371
+ >
372
+ <PCCheckbox
373
+ label={requestTypeLabels[type]}
374
+ checked={selectedRequestTypes.includes(type)}
375
+ onChange={(e) => {
376
+ if (e.target.checked) {
377
+ setSelectedRequestTypes([
378
+ ...selectedRequestTypes,
379
+ type,
380
+ ]);
381
+ } else {
382
+ setSelectedRequestTypes(
383
+ selectedRequestTypes.filter((rt) => rt !== type)
384
+ );
385
+ }
386
+ }}
387
+ />
388
+ </div>
389
+ ))}
390
+ </div>
391
+ </div>
392
+ }
393
+ >
394
+ <PCButton
395
+ variant="outline"
396
+ style={{
397
+ width: 'auto',
398
+ justifyContent: 'space-between',
399
+ ...pcStyles.gapSm,
400
+ }}
401
+ >
402
+ <span>{t('filter')}</span>
403
+ <FiFilter size={16} />
404
+ </PCButton>
405
+ </PCPopover>
406
+ </div>
407
+ </div>
408
+ </div>
409
+ <GrievanceTable
410
+ key={`grievance-table-${activeTab}-${selectedRequestTypes.join(',')}-${searchQuery}`}
411
+ table={table}
412
+ isLoading={isLoading}
413
+ pageIndex={pageIndex}
414
+ pageSize={pageSize}
415
+ />
416
+
417
+ {/* Always show pagination, even during loading */}
418
+ <GrievanceTablePagination
419
+ table={table}
420
+ onPageIndexChange={setPageIndex}
421
+ onPageSizeChange={(newSize) => {
422
+ setPageSize(newSize);
423
+ setPageIndex(0);
424
+ }}
425
+ />
426
+ <CaseDetailsModal
427
+ isOpen={isModalOpen}
428
+ onClose={() => setIsModalOpen(false)}
429
+ selectedCase={selectedCase}
430
+ />
431
+ </div>
432
+ );
433
+ }