@redacto.io/consent-sdk-react 9.2.0 → 9.3.0-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 (41) hide show
  1. package/.turbo/turbo-build.log +25 -26
  2. package/.turbo/turbo-test.log +203 -0
  3. package/.turbo/turbo-verify-pack.log +6 -0
  4. package/CHANGELOG.md +12 -0
  5. package/dist/privacy-center.js +3242 -2956
  6. package/dist/privacy-center.mjs +3068 -2774
  7. package/package.json +3 -3
  8. package/src/RedactoPrivacyCenter/PrivacyCenterLayout.tsx +6 -3
  9. package/src/RedactoPrivacyCenter/components/AppShell/AppShell.tsx +36 -5
  10. package/src/RedactoPrivacyCenter/components/AppShell/TopAppBar.tsx +3 -2
  11. package/src/RedactoPrivacyCenter/components/Form.tsx +127 -8
  12. package/src/RedactoPrivacyCenter/components/PrivacyCenterEmptyState.tsx +76 -0
  13. package/src/RedactoPrivacyCenter/components/ReceiptsList.tsx +3 -41
  14. package/src/RedactoPrivacyCenter/context/DataAvailabilityContext.tsx +169 -0
  15. package/src/RedactoPrivacyCenter/locales/as/translation.json +2 -0
  16. package/src/RedactoPrivacyCenter/locales/bho/translation.json +2 -0
  17. package/src/RedactoPrivacyCenter/locales/bn/translation.json +2 -0
  18. package/src/RedactoPrivacyCenter/locales/brx/translation.json +2 -0
  19. package/src/RedactoPrivacyCenter/locales/doi/translation.json +2 -0
  20. package/src/RedactoPrivacyCenter/locales/en/translation.json +2 -0
  21. package/src/RedactoPrivacyCenter/locales/gom/translation.json +2 -0
  22. package/src/RedactoPrivacyCenter/locales/gu/translation.json +2 -0
  23. package/src/RedactoPrivacyCenter/locales/hi/translation.json +2 -0
  24. package/src/RedactoPrivacyCenter/locales/kn/translation.json +2 -0
  25. package/src/RedactoPrivacyCenter/locales/ks/translation.json +2 -0
  26. package/src/RedactoPrivacyCenter/locales/mai/translation.json +2 -0
  27. package/src/RedactoPrivacyCenter/locales/ml/translation.json +2 -0
  28. package/src/RedactoPrivacyCenter/locales/mni-Mtei/translation.json +2 -0
  29. package/src/RedactoPrivacyCenter/locales/mr/translation.json +2 -0
  30. package/src/RedactoPrivacyCenter/locales/ne/translation.json +2 -0
  31. package/src/RedactoPrivacyCenter/locales/or/translation.json +2 -0
  32. package/src/RedactoPrivacyCenter/locales/pa/translation.json +2 -0
  33. package/src/RedactoPrivacyCenter/locales/sa/translation.json +2 -0
  34. package/src/RedactoPrivacyCenter/locales/sat/translation.json +2 -0
  35. package/src/RedactoPrivacyCenter/locales/sd/translation.json +2 -0
  36. package/src/RedactoPrivacyCenter/locales/ta/translation.json +2 -0
  37. package/src/RedactoPrivacyCenter/locales/te/translation.json +2 -0
  38. package/src/RedactoPrivacyCenter/locales/ur/translation.json +2 -0
  39. package/src/RedactoPrivacyCenter/styles/injectStyles.ts +1 -1
  40. package/tests/DataAvailabilityContext.test.tsx +123 -0
  41. package/tests/Form.test.tsx +93 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redacto.io/consent-sdk-react",
3
- "version": "9.2.0",
3
+ "version": "9.3.0-beta.1",
4
4
  "main": "dist/index.cjs",
5
5
  "module": "dist/index.mjs",
6
6
  "types": "dist/index.d.ts",
@@ -32,11 +32,11 @@
32
32
  "@types/lodash": "^4.17.0",
33
33
  "@types/react": "^19.1.3",
34
34
  "@types/react-dom": "^19.1.3",
35
- "@vitest/coverage-v8": "3.1.3",
35
+ "@vitest/coverage-v8": "^4.1.8",
36
36
  "jsdom": "^26.1.0",
37
37
  "tsup": "^8.4.0",
38
38
  "typescript": "5.8.2",
39
- "vitest": "^3.1.3"
39
+ "vitest": "^4.1.8"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "react": "^16.8.0 || ^17 || ^18 || ^19",
@@ -1,5 +1,6 @@
1
1
  import { useEffect, useState, useRef, type ReactNode } from 'react';
2
2
  import { OrganizationProvider } from './context/OrganizationContext';
3
+ import { DataAvailabilityProvider } from './context/DataAvailabilityContext';
3
4
  import type { OrganizationDetail } from './lib/types';
4
5
  import {
5
6
  checkAndRefreshAuth,
@@ -161,9 +162,11 @@ export function PrivacyCenterLayout({ children, slug, onBack }: PrivacyCenterLay
161
162
 
162
163
  return (
163
164
  <OrganizationProvider organization={organization}>
164
- <AppShell onBack={onBack}>
165
- {children}
166
- </AppShell>
165
+ <DataAvailabilityProvider>
166
+ <AppShell onBack={onBack}>
167
+ {children}
168
+ </AppShell>
169
+ </DataAvailabilityProvider>
167
170
  </OrganizationProvider>
168
171
  );
169
172
  }
@@ -19,6 +19,9 @@ import { TopAppBar } from './TopAppBar';
19
19
  import { SidebarNav } from './SidebarNav';
20
20
  import { MobileNavDrawer } from './MobileNavDrawer';
21
21
  import { PageHeader } from './PageHeader';
22
+ import { PCLoader } from '../../ui';
23
+ import { useDataAvailability } from '../../context/DataAvailabilityContext';
24
+ import { PrivacyCenterEmptyState } from '../PrivacyCenterEmptyState';
22
25
  import type { PrivacyCenterLayoutProps } from '../types';
23
26
 
24
27
  export default function AppShell({
@@ -35,6 +38,13 @@ export default function AppShell({
35
38
 
36
39
  const isMobile = useMediaQuery('(max-width: 639px)');
37
40
 
41
+ // Keep the shell minimal — top bar + a content spinner — until we've CONFIRMED
42
+ // the user has data. Hiding the sidebar/tabs/bell during 'checking' (not just
43
+ // 'empty') stops the nav from flashing in on refresh only to disappear when
44
+ // the API resolves to "no data". The branded top bar stays visible throughout.
45
+ const { status } = useDataAvailability();
46
+ const hideNav = status !== 'has-data';
47
+
38
48
  // Close mobile drawer when crossing the breakpoint
39
49
  useEffect(() => {
40
50
  if (!isMobile && isMobileNavOpen) setMobileNavOpen(false);
@@ -110,14 +120,15 @@ export default function AppShell({
110
120
  <main className="pc-root pc-shell-root" style={rootStyle}>
111
121
  <TopAppBar
112
122
  ref={hamburgerRef}
113
- showHamburger={isMobile}
123
+ showHamburger={isMobile && !hideNav}
114
124
  onOpenMobileNav={() => setMobileNavOpen(true)}
115
125
  locale={locale}
116
126
  onLocaleChange={setLocale}
117
127
  onBack={onBack}
128
+ showNotifications={!hideNav}
118
129
  />
119
130
  <div className="pc-shell-body" style={bodyStyle}>
120
- {!isMobile && (
131
+ {!isMobile && !hideNav && (
121
132
  <SidebarNav
122
133
  className={sidebarClassName}
123
134
  isExpanded={sidebar.isExpanded}
@@ -126,8 +137,28 @@ export default function AppShell({
126
137
  )}
127
138
  <section className="pc-shell-content" style={mainStyle}>
128
139
  <div className={contentClassName} style={contentWrapperStyle}>
129
- <PageHeader />
130
- <div style={{ flex: 1, minWidth: 0, width: '100%' }}>{children}</div>
140
+ {status === 'checking' ? (
141
+ <div
142
+ style={{
143
+ ...pcStyles.flex,
144
+ ...pcStyles.itemsCenter,
145
+ ...pcStyles.justifyCenter,
146
+ flex: 1,
147
+ minHeight: 240,
148
+ }}
149
+ >
150
+ <PCLoader variant="spinner" color="info" size="lg" />
151
+ </div>
152
+ ) : status === 'empty' ? (
153
+ <div style={{ flex: 1, minWidth: 0, width: '100%' }}>
154
+ <PrivacyCenterEmptyState />
155
+ </div>
156
+ ) : (
157
+ <>
158
+ <PageHeader />
159
+ <div style={{ flex: 1, minWidth: 0, width: '100%' }}>{children}</div>
160
+ </>
161
+ )}
131
162
  <div
132
163
  style={{
133
164
  ...pcStyles.flex,
@@ -143,7 +174,7 @@ export default function AppShell({
143
174
  </div>
144
175
  </section>
145
176
  </div>
146
- {isMobile && (
177
+ {isMobile && !hideNav && (
147
178
  <MobileNavDrawer
148
179
  isOpen={isMobileNavOpen}
149
180
  onClose={() => setMobileNavOpen(false)}
@@ -26,10 +26,11 @@ type TopAppBarProps = {
26
26
  locale: string;
27
27
  onLocaleChange: (next: string) => void;
28
28
  onBack?: 'back' | 'signout';
29
+ showNotifications?: boolean;
29
30
  };
30
31
 
31
32
  export const TopAppBar = forwardRef<HTMLButtonElement, TopAppBarProps>(function TopAppBar(
32
- { onOpenMobileNav, showHamburger, locale, onLocaleChange, onBack },
33
+ { onOpenMobileNav, showHamburger, locale, onLocaleChange, onBack, showNotifications = true },
33
34
  hamburgerRef,
34
35
  ) {
35
36
  const { t } = useTranslation();
@@ -170,7 +171,7 @@ export const TopAppBar = forwardRef<HTMLButtonElement, TopAppBarProps>(function
170
171
  onChange={handleLanguageChange}
171
172
  />
172
173
  )}
173
- <NotificationBell />
174
+ {showNotifications && <NotificationBell />}
174
175
  <UserMenu onBack={onBack} />
175
176
  </div>
176
177
  </header>
@@ -9,6 +9,7 @@ import { AiOutlineClose } from 'react-icons/ai';
9
9
  import { PiUploadBold } from 'react-icons/pi';
10
10
  import { toast } from '../utils/toast';
11
11
  import { PCButton, PCCheckbox, PCInput, PCLoader, PCSelect, PCText, PCTextarea } from '../ui';
12
+ import { PCModal } from '../ui/PCModal';
12
13
  import { buildGroupDataElements, buildGroupPurposes } from './groupHelpers';
13
14
  import { pcStyles } from '../styles/pcStyles';
14
15
  import { createCaseRequest, getFormData } from '../lib/api';
@@ -46,7 +47,8 @@ import {
46
47
  import PurposeSelectionSection from './DataRequest/PurposeSelectionSection';
47
48
  import RequestSubmit from './RequestSubmit';
48
49
  import type { CaseHistoryProps } from './types';
49
- import { FiChevronDown, FiChevronUp, FiX } from 'react-icons/fi';
50
+ import { FiAlertCircle, FiChevronDown, FiChevronUp, FiX } from 'react-icons/fi';
51
+ import { MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL } from './constant';
50
52
 
51
53
  const getVar = (varName: string): string => `var(${varName})`;
52
54
 
@@ -219,6 +221,10 @@ export default function Form({
219
221
  const [isTranslating, setIsTranslating] = useState(false);
220
222
  const [isRequestDetailsExpanded, setIsRequestDetailsExpanded] =
221
223
  useState(true);
224
+ const [isErasureConsentModalOpen, setIsErasureConsentModalOpen] =
225
+ useState(false);
226
+ const [revokeConsentOnFulfilment, setRevokeConsentOnFulfilment] =
227
+ useState(false);
222
228
 
223
229
  const resetFileInput = () => {
224
230
  if (fileInputRef.current) {
@@ -537,17 +543,52 @@ export default function Form({
537
543
  return translatedName;
538
544
  }, [data]);
539
545
 
546
+ // Purpose names selected in an ERASURE request — surfaced in the
547
+ // consent-revoke modal and the post-submit notice. When the list is
548
+ // empty or too long to enumerate, fall back to a generic phrase.
549
+ const selectedErasurePurposeList = useMemo(
550
+ () =>
551
+ selectedPurposes
552
+ .filter((p) => p.selected || p.data_elements.some((de) => de.selected))
553
+ .map((p) => p.name)
554
+ .filter(Boolean),
555
+ [selectedPurposes]
556
+ );
557
+
558
+ const selectedErasurePurposeNames = useMemo(() => {
559
+ if (
560
+ selectedErasurePurposeList.length === 0 ||
561
+ selectedErasurePurposeList.length > MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL
562
+ ) {
563
+ return t('revokeConsentModalYourPurposes');
564
+ }
565
+ return selectedErasurePurposeList.join(', ');
566
+ }, [selectedErasurePurposeList, t]);
567
+
540
568
  const handleSubmitClick = () => {
541
569
  if (!isFormValid) {
542
570
  return;
543
571
  }
544
- // For ERASURE, set revoke_consent_on_fulfilment=true so the
545
- // backend's auto-revoke task fires when the case is fulfilled.
546
- // Erasing data without revoking the consent that authorized its
547
- // processing is incoherent per DPDPA Sec 12. Other request types
548
- // (ACCESS / CORRECTION / NOMINATION / GRIEVANCE) do not engage
549
- // the auto-revoke path on the backend and pass false here.
550
- void handleSubmit(requestType === RequestTypeEnum.ERASURE);
572
+ // ERASURE asks the data principal whether to also revoke consent on
573
+ // fulfilment — the choice is made in the modal and threaded into the
574
+ // payload via handleErasureModalChoice. Other request types
575
+ // (ACCESS / CORRECTION / NOMINATION / GRIEVANCE) submit directly.
576
+ if (requestType === RequestTypeEnum.ERASURE) {
577
+ setIsErasureConsentModalOpen(true);
578
+ return;
579
+ }
580
+ setRevokeConsentOnFulfilment(false);
581
+ void handleSubmit(false);
582
+ };
583
+
584
+ const handleErasureModalChoice = (revoke: boolean) => {
585
+ setRevokeConsentOnFulfilment(revoke);
586
+ setIsErasureConsentModalOpen(false);
587
+ void handleSubmit(revoke);
588
+ };
589
+
590
+ const handleErasureModalClose = () => {
591
+ setIsErasureConsentModalOpen(false);
551
592
  };
552
593
 
553
594
  /**
@@ -959,6 +1000,8 @@ export default function Form({
959
1000
  result={submittedCase}
960
1001
  contact={formData?.contact}
961
1002
  onCreateRequest={onCreateRequest}
1003
+ revokeConsentOnFulfilment={revokeConsentOnFulfilment}
1004
+ revokedPurposeNames={selectedErasurePurposeNames}
962
1005
  />
963
1006
  ) : (
964
1007
  <div>
@@ -1616,6 +1659,82 @@ export default function Form({
1616
1659
  </div>
1617
1660
  </div>
1618
1661
  )}
1662
+ <PCModal
1663
+ isOpen={isErasureConsentModalOpen}
1664
+ onClose={handleErasureModalClose}
1665
+ size="md"
1666
+ showCloseButton={false}
1667
+ footer={
1668
+ <div className="pc-erasure-modal-footer">
1669
+ <PCButton
1670
+ variant="secondary"
1671
+ onClick={() => handleErasureModalChoice(false)}
1672
+ disabled={isLoading}
1673
+ >
1674
+ {t('revokeConsentNo')}
1675
+ </PCButton>
1676
+ <PCButton
1677
+ variant="primary"
1678
+ onClick={() => handleErasureModalChoice(true)}
1679
+ disabled={isLoading}
1680
+ isLoading={isLoading}
1681
+ >
1682
+ {t('revokeConsentYes')}
1683
+ </PCButton>
1684
+ </div>
1685
+ }
1686
+ >
1687
+ <div className="pc-erasure-modal-content">
1688
+ <div className="pc-erasure-modal-header">
1689
+ <div className="pc-erasure-modal-icon">
1690
+ <FiAlertCircle size={24} />
1691
+ </div>
1692
+ <p className="pc-erasure-modal-title">
1693
+ {t('revokeConsentModalTitle')}
1694
+ </p>
1695
+ <p className="pc-erasure-modal-question">
1696
+ {t('revokeConsentModalQuestion')}
1697
+ </p>
1698
+ </div>
1699
+
1700
+ <div className="pc-erasure-modal-purposes">
1701
+ <p className="pc-erasure-modal-purposes-label">
1702
+ {t('revokeConsentModalPurposesLabel')}
1703
+ </p>
1704
+ {selectedErasurePurposeList.length > 0 ? (
1705
+ <>
1706
+ {selectedErasurePurposeList
1707
+ .slice(0, MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL)
1708
+ .map((name) => (
1709
+ <div key={name} className="pc-erasure-modal-purpose-item">
1710
+ {name}
1711
+ </div>
1712
+ ))}
1713
+ {selectedErasurePurposeList.length > MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL && (
1714
+ <span className="pc-erasure-modal-purposes-placeholder">
1715
+ {t('revokeConsentModalAndMore', {
1716
+ count: selectedErasurePurposeList.length - MAX_DISPLAYED_PURPOSE_NAMES_IN_ERASURE_MODAL,
1717
+ })}
1718
+ </span>
1719
+ )}
1720
+ </>
1721
+ ) : (
1722
+ <span className="pc-erasure-modal-purposes-placeholder">
1723
+ {t('revokeConsentModalYourPurposes')}
1724
+ </span>
1725
+ )}
1726
+ </div>
1727
+
1728
+ <div className="pc-erasure-modal-warning">
1729
+ <p className="pc-erasure-modal-warning-text">
1730
+ <span className="pc-erasure-modal-warning-label">
1731
+ {t('revokeConsentModalIfYesLabel')}
1732
+ </span>{' '}
1733
+ {t('revokeConsentModalIfYesBody')}
1734
+ </p>
1735
+ </div>
1736
+ </div>
1737
+ </PCModal>
1619
1738
  </>
1620
1739
  );
1621
1740
  }
@@ -0,0 +1,76 @@
1
+ import { useTranslation } from 'react-i18next';
2
+ import character from '../assets/character.svg';
3
+ import { PCText, PCTitle } from '../ui';
4
+ import { pcStyles } from '../styles/pcStyles';
5
+ import { getVar } from '../lib/utils';
6
+
7
+ /**
8
+ * Full-screen content shown (in place of the consent manager / tabs) when the
9
+ * user has no data anywhere — no consents, no data-request cases, no activity.
10
+ * Purely presentational: a friendly illustration, heading, and subtitle.
11
+ */
12
+ export function PrivacyCenterEmptyState() {
13
+ const { t } = useTranslation();
14
+
15
+ return (
16
+ <div
17
+ style={{
18
+ ...pcStyles.flex,
19
+ ...pcStyles.flexCol,
20
+ ...pcStyles.itemsCenter,
21
+ ...pcStyles.justifyCenter,
22
+ ...pcStyles.textCenter,
23
+ width: '100%',
24
+ maxWidth: 460,
25
+ margin: '0 auto',
26
+ minHeight: 'min(62vh, 540px)',
27
+ paddingTop: getVar('--pc-spacing-2xl'),
28
+ paddingBottom: getVar('--pc-spacing-2xl'),
29
+ gap: getVar('--pc-spacing-xl'),
30
+ }}
31
+ >
32
+ <div
33
+ aria-hidden="true"
34
+ style={{
35
+ display: 'flex',
36
+ alignItems: 'center',
37
+ justifyContent: 'center',
38
+ width: 168,
39
+ height: 168,
40
+ borderRadius: '50%',
41
+ background:
42
+ 'color-mix(in srgb, var(--pc-primary) 8%, var(--pc-bg-shell))',
43
+ flexShrink: 0,
44
+ }}
45
+ >
46
+ <img
47
+ src={character}
48
+ alt=""
49
+ style={{ width: 104, height: 'auto', display: 'block' }}
50
+ />
51
+ </div>
52
+
53
+ <div
54
+ style={{
55
+ ...pcStyles.flex,
56
+ ...pcStyles.flexCol,
57
+ ...pcStyles.itemsCenter,
58
+ gap: getVar('--pc-spacing-sm'),
59
+ }}
60
+ >
61
+ <PCTitle as="h2" style={{ margin: 0 }}>
62
+ {t('noDataTitle')}
63
+ </PCTitle>
64
+ <PCText
65
+ as="p"
66
+ size="base"
67
+ color="secondary"
68
+ align="center"
69
+ style={{ margin: 0, maxWidth: 400 }}
70
+ >
71
+ {t('noDataDescription')}
72
+ </PCText>
73
+ </div>
74
+ </div>
75
+ );
76
+ }
@@ -13,7 +13,7 @@ import { useAuth } from '../context/AuthContext';
13
13
  import { useBaseUrl } from '../context/BaseUrlContext';
14
14
  import { useCurrentLanguage } from '../lib/useCurrentLanguage';
15
15
  import { toast } from '../utils/toast';
16
- import type { Receipt, ReceiptEventType, ReceiptPurposeStatus } from '../lib/types';
16
+ import type { Receipt, ReceiptEventType } from '../lib/types';
17
17
  import { useReceiptColumns } from './receiptColumns';
18
18
 
19
19
  /**
@@ -89,7 +89,6 @@ export default function ReceiptsList() {
89
89
 
90
90
  // ─── Filters ────────────────────────────────────────────────────────────────
91
91
  const [eventType, setEventType] = useState<ReceiptEventType | ''>('');
92
- const [status, setStatus] = useState<ReceiptPurposeStatus | ''>('');
93
92
  const [datePreset, setDatePreset] = useState<string>(''); // '' | '1' | '7' | '30' | '90' | 'custom'
94
93
  const [customFrom, setCustomFrom] = useState<string>('');
95
94
  const [customTo, setCustomTo] = useState<string>('');
@@ -104,15 +103,6 @@ export default function ReceiptsList() {
104
103
  ],
105
104
  [t]
106
105
  );
107
- const statusOptions = useMemo<SelectOption[]>(
108
- () => [
109
- { value: '', label: t('all') },
110
- { value: 'CONSENTED', label: t('consented') },
111
- { value: 'DECLINED', label: t('declined') },
112
- { value: 'WITHDRAWN', label: t('withdrawn') },
113
- ],
114
- [t]
115
- );
116
106
  const dateOptions = useMemo<SelectOption[]>(
117
107
  () => [
118
108
  { value: '', label: t('allTime') },
@@ -126,9 +116,8 @@ export default function ReceiptsList() {
126
116
  );
127
117
 
128
118
  const eventValue = eventTypeOptions.find((o) => o.value === eventType) ?? null;
129
- const statusValue = statusOptions.find((o) => o.value === status) ?? null;
130
119
  const dateValue = dateOptions.find((o) => o.value === datePreset) ?? null;
131
- const hasActiveFilters = Boolean(eventType || status || datePreset);
120
+ const hasActiveFilters = Boolean(eventType || datePreset);
132
121
  const isCustomRangeInvalid = useMemo(
133
122
  () =>
134
123
  datePreset === 'custom' &&
@@ -142,9 +131,6 @@ export default function ReceiptsList() {
142
131
  if (eventType && eventValue?.label) {
143
132
  filters.push({ id: 'event', label: t('event'), value: eventValue.label });
144
133
  }
145
- if (status && statusValue?.label) {
146
- filters.push({ id: 'status', label: t('status'), value: statusValue.label });
147
- }
148
134
  if (datePreset && dateValue?.label) {
149
135
  let dateLabel = dateValue.label;
150
136
  if (datePreset === 'custom') {
@@ -156,16 +142,12 @@ export default function ReceiptsList() {
156
142
  }
157
143
 
158
144
  return filters;
159
- }, [eventType, eventValue, status, statusValue, datePreset, dateValue, customFrom, customTo, t]);
145
+ }, [eventType, eventValue, datePreset, dateValue, customFrom, customTo, t]);
160
146
 
161
147
  const handleEventChange = (opt: SelectOption | null) => {
162
148
  setEventType((opt ? String(opt.value) : '') as ReceiptEventType | '');
163
149
  setCurrentPage(1);
164
150
  };
165
- const handleStatusChange = (opt: SelectOption | null) => {
166
- setStatus((opt ? String(opt.value) : '') as ReceiptPurposeStatus | '');
167
- setCurrentPage(1);
168
- };
169
151
  const handleDateChange = (opt: SelectOption | null) => {
170
152
  const nextDatePreset = opt ? String(opt.value) : '';
171
153
  setDatePreset(nextDatePreset);
@@ -177,7 +159,6 @@ export default function ReceiptsList() {
177
159
  };
178
160
  const handleClearFilters = () => {
179
161
  setEventType('');
180
- setStatus('');
181
162
  setDatePreset('');
182
163
  setCustomFrom('');
183
164
  setCustomTo('');
@@ -199,7 +180,6 @@ export default function ReceiptsList() {
199
180
  skip,
200
181
  limit: size,
201
182
  event_type: eventType || undefined,
202
- status: status || undefined,
203
183
  created_after,
204
184
  created_before,
205
185
  language: langParam,
@@ -229,7 +209,6 @@ export default function ReceiptsList() {
229
209
  currentPage,
230
210
  pageSize,
231
211
  eventType,
232
- status,
233
212
  datePreset,
234
213
  customFrom,
235
214
  customTo,
@@ -375,23 +354,6 @@ export default function ReceiptsList() {
375
354
  dropdownMatchTriggerWidth
376
355
  style={{ width: '100%' }}
377
356
  />
378
- <PCSelect
379
- className="pc-filter-select pc-receipts-filter-control"
380
- size="sm"
381
- label={t('status')}
382
- placeholder={t('all')}
383
- options={statusOptions}
384
- value={statusValue}
385
- onChange={handleStatusChange}
386
- onClear={() => {
387
- setStatus('');
388
- setCurrentPage(1);
389
- }}
390
- clearable={Boolean(status)}
391
- showSelectedCheck
392
- dropdownMatchTriggerWidth
393
- style={{ width: '100%' }}
394
- />
395
357
  <PCSelect
396
358
  className="pc-filter-select pc-receipts-filter-control"
397
359
  size="sm"