{
+ const { t } = useTranslation(['common', 'report'])
+ const [modalData, setModalData] = useState(null)
+ const alertRef = useRef()
+
+ const [isScrollingUp, setIsScrollingUp] = useState(false)
+ const [lastScrollTop, setLastScrollTop] = useState(0)
+
+ const scrollToTopOrBottom = () => {
+ if (isScrollingUp) {
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth'
+ })
+ } else {
+ window.scrollTo({
+ top: document.documentElement.scrollHeight,
+ behavior: 'smooth'
+ })
+ }
+ }
+
+ const handleScroll = useCallback(() => {
+ const scrollTop = window.pageYOffset || document.documentElement.scrollTop
+ setIsScrollingUp(scrollTop < lastScrollTop || scrollTop === 0)
+ setLastScrollTop(scrollTop)
+ }, [lastScrollTop])
+
+ useEffect(() => {
+ window.addEventListener('scroll', handleScroll)
+ return () => window.removeEventListener('scroll', handleScroll)
+ }, [handleScroll])
+
+ const { data: currentUser, isLoading: isCurrentUserLoading } =
+ useCurrentUser()
+ const isGovernmentUser = currentUser?.isGovernmentUser
+
+ const currentStatus = reportData.report.currentStatus?.status
+ const { data: orgData, isLoading } = useOrganization(
+ reportData.report.organizationId
+ )
+
+ if (isLoading || isCurrentUserLoading) {
+ return
+ }
+
+ if (isError) {
+ return (
+ <>
+
+ {t('report:errorRetrieving')}
+ >
+ )
+ }
+
+ return (
+ <>
+
+
+ setModalData(null)}
+ data={modalData}
+ />
+
+
+ {reportData?.report.compliancePeriod.description}
+ {t('report:complianceReport')} -
+ {reportData?.report.nickname}
+
+
+ Status: {currentStatus}
+
+
+
+
+
+
+
+ {isScrollingUp ? (
+
+ ) : (
+
+ )}
+
+
+
+ >
+ )
+}
diff --git a/frontend/src/views/ComplianceReports/__tests__/ComplianceReportViewSelector.test.jsx b/frontend/src/views/ComplianceReports/__tests__/ComplianceReportViewSelector.test.jsx
new file mode 100644
index 000000000..4ceada497
--- /dev/null
+++ b/frontend/src/views/ComplianceReports/__tests__/ComplianceReportViewSelector.test.jsx
@@ -0,0 +1,146 @@
+import React from 'react'
+import { render, screen, waitFor } from '@testing-library/react'
+import { describe, it, expect, beforeEach, vi } from 'vitest'
+import { ComplianceReportViewSelector } from '../ComplianceReportViewSelector.jsx'
+import * as useComplianceReportsHook from '@/hooks/useComplianceReports'
+import * as useCurrentUserHook from '@/hooks/useCurrentUser'
+import { wrapper } from '@/tests/utils/wrapper'
+
+vi.mock('react-router-dom', async () => {
+ const actual = await vi.importActual('react-router-dom')
+ return {
+ ...actual,
+ useParams: vi.fn()
+ }
+})
+
+vi.mock('@/hooks/useComplianceReports')
+vi.mock('@/hooks/useCurrentUser')
+
+vi.mock('@/components/Loading', () => ({
+ default: () => Loading...
+}))
+
+vi.mock('@/views/ComplianceReports/ViewLegacyComplianceReport', () => ({
+ ViewLegacyComplianceReport: () => Legacy Report View
+}))
+
+vi.mock('@/views/ComplianceReports/EditViewComplianceReport', () => ({
+ EditViewComplianceReport: () => Edit Compliance Report
+}))
+
+// Import useParams after mocking so it's already a mock
+import { useParams } from 'react-router-dom'
+
+describe('ViewComplianceReportBrancher', () => {
+ const setupMocks = ({
+ currentUser = { organization: { organizationId: '123' } },
+ isCurrentUserLoading = false,
+ reportData = {
+ report: {
+ legacyId: null,
+ currentStatus: { status: 'DRAFT' }
+ }
+ },
+ isReportLoading = false,
+ isError = false,
+ error = null,
+ complianceReportId = '123'
+ } = {}) => {
+ // Set the return value for useParams
+ useParams.mockReturnValue({ complianceReportId })
+
+ // Mock useCurrentUser
+ useCurrentUserHook.useCurrentUser.mockReturnValue({
+ data: currentUser,
+ isLoading: isCurrentUserLoading
+ })
+
+ // Mock useGetComplianceReport
+ useComplianceReportsHook.useGetComplianceReport.mockReturnValue({
+ data: reportData,
+ isLoading: isReportLoading,
+ isError,
+ error
+ })
+ }
+
+ beforeEach(() => {
+ vi.resetAllMocks()
+ })
+
+ it('renders loading when user is loading', async () => {
+ setupMocks({ isCurrentUserLoading: true })
+ render(, { wrapper })
+
+ await waitFor(() => {
+ expect(screen.getByText('Loading...')).toBeInTheDocument()
+ })
+ })
+
+ it('renders loading when report is loading', async () => {
+ setupMocks({ isReportLoading: true })
+ render(, { wrapper })
+
+ await waitFor(() => {
+ expect(screen.getByText('Loading...')).toBeInTheDocument()
+ })
+ })
+
+ it('renders ViewLegacyComplianceReport when legacyId is present', async () => {
+ setupMocks({
+ reportData: {
+ report: {
+ legacyId: 999,
+ currentStatus: { status: 'DRAFT' }
+ }
+ }
+ })
+
+ render(, { wrapper })
+
+ await waitFor(() => {
+ expect(screen.getByText('Legacy Report View')).toBeInTheDocument()
+ expect(
+ screen.queryByText('Edit Compliance Report')
+ ).not.toBeInTheDocument()
+ })
+ })
+
+ it('renders EditViewComplianceReport when legacyId is null/undefined', async () => {
+ setupMocks({
+ reportData: {
+ report: {
+ currentStatus: { status: 'DRAFT' }
+ // No legacyId means it should render the EditViewComplianceReport
+ }
+ }
+ })
+
+ render(, { wrapper })
+
+ await waitFor(() => {
+ expect(screen.getByText('Edit Compliance Report')).toBeInTheDocument()
+ expect(screen.queryByText('Legacy Report View')).not.toBeInTheDocument()
+ })
+ })
+
+ it('passes error and isError props to the rendered component', async () => {
+ const testError = { message: 'Test error' }
+ setupMocks({
+ isError: true,
+ error: testError,
+ reportData: {
+ report: {
+ currentStatus: { status: 'DRAFT' }
+ }
+ }
+ })
+
+ render(, { wrapper })
+
+ await waitFor(() => {
+ expect(screen.getByText('Edit Compliance Report')).toBeInTheDocument()
+ })
+ })
+})
diff --git a/frontend/src/views/ComplianceReports/__tests__/EditViewComplianceReports.test.jsx b/frontend/src/views/ComplianceReports/__tests__/EditViewComplianceReports.test.jsx
index cb50dbee6..a3e38b0df 100644
--- a/frontend/src/views/ComplianceReports/__tests__/EditViewComplianceReports.test.jsx
+++ b/frontend/src/views/ComplianceReports/__tests__/EditViewComplianceReports.test.jsx
@@ -2,9 +2,9 @@ import React from 'react'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { EditViewComplianceReport } from '../EditViewComplianceReport'
-import * as useComplianceReportsHook from '@/hooks/useComplianceReports'
import * as useCurrentUserHook from '@/hooks/useCurrentUser'
import * as useOrganizationHook from '@/hooks/useOrganization'
+import * as useComplianceReportsHook from '@/hooks/useComplianceReports'
import { COMPLIANCE_REPORT_STATUSES } from '@/constants/statuses'
import { wrapper } from '@/tests/utils/wrapper'
@@ -89,17 +89,17 @@ describe('EditViewComplianceReport', () => {
isLoading: false,
hasRoles: mockHasRoles
},
- complianceReport: {
- data: {
- report: {
- organizationId: '123',
- currentStatus: { status: COMPLIANCE_REPORT_STATUSES.DRAFT }
- },
- chain: []
+ reportData: {
+ report: {
+ organizationId: '123',
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.DRAFT },
+ history: [],
+ nickname: 'Test Report'
},
- isLoading: false,
- isError: false
+ chain: []
},
+ isError: false,
+ error: null,
organization: {
data: {
name: 'Test Org',
@@ -117,10 +117,6 @@ describe('EditViewComplianceReport', () => {
}
},
isLoading: false
- },
- createSupplementalReport: {
- mutate: vi.fn(),
- isLoading: false
}
}
@@ -131,9 +127,6 @@ describe('EditViewComplianceReport', () => {
vi.mocked(useCurrentUserHook.useCurrentUser).mockReturnValue(
mocks.currentUser
)
- vi.mocked(useComplianceReportsHook.useGetComplianceReport).mockReturnValue(
- mocks.complianceReport
- )
vi.mocked(useOrganizationHook.useOrganization).mockReturnValue(
mocks.organization
)
@@ -142,23 +135,43 @@ describe('EditViewComplianceReport', () => {
).mockReturnValue({ mutate: vi.fn() })
vi.mocked(
useComplianceReportsHook.useCreateSupplementalReport
- ).mockReturnValue(mocks.createSupplementalReport)
+ ).mockReturnValue({
+ mutate: vi.fn(),
+ isLoading: false
+ })
+
+ return mocks
}
beforeEach(() => {
vi.resetAllMocks()
- setupMocks()
})
it('renders the component', async () => {
- render(, { wrapper })
+ const mocks = setupMocks()
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(screen.getByText(/2023.*complianceReport/i)).toBeInTheDocument()
})
})
it('renders report components', async () => {
- render(, { wrapper })
+ const mocks = setupMocks()
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(screen.getByText('Report Details')).toBeInTheDocument()
expect(screen.getByText('Compliance Report Summary')).toBeInTheDocument()
@@ -167,44 +180,61 @@ describe('EditViewComplianceReport', () => {
})
it('displays an alert message when location state has a message', async () => {
- setupMocks({
+ const mocks = setupMocks({
useLocation: { state: { message: 'Test alert', severity: 'success' } }
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(screen.getByText('Test alert')).toBeInTheDocument()
})
})
it('displays an error message when there is an error fetching the report', async () => {
- setupMocks({
- complianceReport: {
- isError: true,
- error: { message: 'Error fetching report' }
- }
+ const mocks = setupMocks({
+ isError: true,
+ error: { message: 'Error fetching report' }
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(screen.getByText('Error fetching report')).toBeInTheDocument()
})
})
it('displays the correct buttons for Submitted status with Analyst role', async () => {
- setupMocks({
- complianceReport: {
- data: {
- report: {
- currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
- },
- chain: []
- }
+ const mocks = setupMocks({
+ reportData: {
+ report: {
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
+ },
+ chain: []
},
currentUser: {
data: { isGovernmentUser: true },
hasRoles: (role) => role === 'Analyst'
}
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(
screen.getByText('report:actionBtns.recommendReportAnalystBtn')
@@ -213,23 +243,28 @@ describe('EditViewComplianceReport', () => {
})
it('displays the correct buttons for Recommended by Analyst status with Compliance Manager role', async () => {
- setupMocks({
- complianceReport: {
- data: {
- report: {
- currentStatus: {
- status: COMPLIANCE_REPORT_STATUSES.RECOMMENDED_BY_ANALYST
- }
- },
- chain: []
- }
+ const mocks = setupMocks({
+ reportData: {
+ report: {
+ currentStatus: {
+ status: COMPLIANCE_REPORT_STATUSES.RECOMMENDED_BY_ANALYST
+ }
+ },
+ chain: []
},
currentUser: {
data: { isGovernmentUser: true },
hasRoles: (role) => role === 'Compliance Manager'
}
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(
screen.getByText('report:actionBtns.recommendReportManagerBtn')
@@ -241,23 +276,28 @@ describe('EditViewComplianceReport', () => {
})
it('displays the correct buttons for Recommended by Manager status with Director role', async () => {
- setupMocks({
- complianceReport: {
- data: {
- report: {
- currentStatus: {
- status: COMPLIANCE_REPORT_STATUSES.RECOMMENDED_BY_MANAGER
- }
- },
- chain: []
- }
+ const mocks = setupMocks({
+ reportData: {
+ report: {
+ currentStatus: {
+ status: COMPLIANCE_REPORT_STATUSES.RECOMMENDED_BY_MANAGER
+ }
+ },
+ chain: []
},
currentUser: {
data: { isGovernmentUser: true },
hasRoles: (role) => role === 'Director'
}
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(
screen.getByText('report:actionBtns.assessReportBtn')
@@ -269,21 +309,26 @@ describe('EditViewComplianceReport', () => {
})
it('displays the correct buttons for Assessed status with Analyst role', async () => {
- setupMocks({
- complianceReport: {
- data: {
- report: {
- currentStatus: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED }
- },
- chain: []
- }
+ const mocks = setupMocks({
+ reportData: {
+ report: {
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED }
+ },
+ chain: []
},
currentUser: {
data: { isGovernmentUser: true },
hasRoles: (role) => role === 'Analyst'
}
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(
screen.getByText('report:actionBtns.reAssessReportBtn')
@@ -292,18 +337,23 @@ describe('EditViewComplianceReport', () => {
})
it('does not display action buttons for non-government users on submitted reports', async () => {
- setupMocks({
- complianceReport: {
- data: {
- report: {
- currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
- },
- chain: []
- }
+ const mocks = setupMocks({
+ reportData: {
+ report: {
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
+ },
+ chain: []
},
currentUser: { data: { isGovernmentUser: false }, hasRoles: () => false }
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(
screen.queryByText('report:actionBtns.recommendReportAnalystBtn')
@@ -321,20 +371,34 @@ describe('EditViewComplianceReport', () => {
})
it('displays internal comments section for government users', async () => {
- setupMocks({
+ const mocks = setupMocks({
currentUser: { data: { isGovernmentUser: true }, hasRoles: () => true }
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(screen.getByText('report:internalComments')).toBeInTheDocument()
})
})
it('does not display internal comments section for non-government users', async () => {
- setupMocks({
+ const mocks = setupMocks({
currentUser: { data: { isGovernmentUser: false }, hasRoles: () => false }
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(
screen.queryByText('report:internalComments')
@@ -343,17 +407,22 @@ describe('EditViewComplianceReport', () => {
})
it('displays ActivityListCard for Draft status', async () => {
- setupMocks({
- complianceReport: {
- data: {
- report: {
- currentStatus: { status: COMPLIANCE_REPORT_STATUSES.DRAFT }
- },
- chain: []
- }
+ const mocks = setupMocks({
+ reportData: {
+ report: {
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.DRAFT }
+ },
+ chain: []
}
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(screen.getByText('Activity Links List')).toBeInTheDocument()
})
@@ -373,8 +442,8 @@ describe('EditViewComplianceReport', () => {
}
]
- vi.mocked(useComplianceReportsHook.useGetComplianceReport).mockReturnValue({
- data: {
+ const mocks = setupMocks({
+ reportData: {
report: {
currentStatus: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED },
history: historyMock
@@ -389,12 +458,17 @@ describe('EditViewComplianceReport', () => {
currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
}
]
- },
- isLoading: false,
- isError: false
+ }
})
- render(, { wrapper })
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
expect(screen.getByText('report:assessment')).toBeInTheDocument()
expect(screen.getByText('report:reportHistory')).toBeInTheDocument()
@@ -405,7 +479,15 @@ describe('EditViewComplianceReport', () => {
})
it('displays scroll-to-top button when scrolling down', async () => {
- render(, { wrapper })
+ const mocks = setupMocks()
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
fireEvent.scroll(window, { target: { pageYOffset: 100 } })
expect(screen.getByLabelText('scroll to bottom')).toBeInTheDocument()
@@ -413,7 +495,15 @@ describe('EditViewComplianceReport', () => {
})
it('displays scroll-to-bottom button when at the top of the page', async () => {
- render(, { wrapper })
+ const mocks = setupMocks()
+ render(
+ ,
+ { wrapper }
+ )
await waitFor(() => {
fireEvent.scroll(window, { target: { pageYOffset: 0 } })
expect(screen.getByLabelText('scroll to top')).toBeInTheDocument()
diff --git a/frontend/src/views/ComplianceReports/__tests__/HistoryCard.test.jsx b/frontend/src/views/ComplianceReports/__tests__/HistoryCard.test.jsx
new file mode 100644
index 000000000..defa965c7
--- /dev/null
+++ b/frontend/src/views/ComplianceReports/__tests__/HistoryCard.test.jsx
@@ -0,0 +1,171 @@
+import React from 'react'
+import { render, screen, waitFor } from '@testing-library/react'
+import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { COMPLIANCE_REPORT_STATUSES } from '@/constants/statuses'
+import { HistoryCard } from '@/views/ComplianceReports/components/HistoryCard.jsx'
+import { wrapper } from '@/tests/utils/wrapper.jsx'
+
+import * as useCurrentUserHook from '@/hooks/useCurrentUser'
+
+// Mock useCurrentUser
+vi.mock('@/hooks/useCurrentUser', () => ({
+ useCurrentUser: vi.fn(() => ({
+ data: { isGovernmentUser: false },
+ isLoading: false
+ }))
+}))
+
+// Mock translation
+vi.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key, opts) => {
+ if (opts && opts.createDate && opts.firstName && opts.lastName) {
+ return `${key}: ${opts.firstName} ${opts.lastName} - ${opts.createDate}`
+ }
+ return key
+ }
+ })
+}))
+
+// Mock timezoneFormatter
+vi.mock('@/utils/formatters', () => ({
+ timezoneFormatter: vi.fn(({ value }) => `formatted-${value}`)
+}))
+
+describe('HistoryCard', () => {
+ const defaultReport = {
+ version: 0,
+ compliancePeriod: { description: '2024' },
+ nickname: 'My Nickname',
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.DRAFT },
+ history: []
+ }
+
+ const renderComponent = (overrides = {}) => {
+ return render(, {
+ wrapper
+ })
+ }
+
+ beforeEach(() => {
+ vi.clearAllMocks()
+ })
+
+ it('renders without history', async () => {
+ renderComponent()
+ // Only the accordion header should be present
+ await waitFor(() => {
+ expect(screen.queryByRole('listitem')).not.toBeInTheDocument()
+ })
+ })
+
+ it('displays compliancePeriod.description and current status if version=0', async () => {
+ renderComponent({
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
+ })
+ await waitFor(() => {
+ expect(
+ screen.getByText(/2024 Compliance Report: SUBMITTED/i)
+ ).toBeInTheDocument()
+ })
+ })
+
+ it('displays nickname and current status if version > 0', async () => {
+ renderComponent({
+ version: 1,
+ nickname: 'My Cool Nickname',
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
+ })
+ await waitFor(() => {
+ expect(
+ screen.getByText(/My Cool Nickname: SUBMITTED/i)
+ ).toBeInTheDocument()
+ })
+ })
+
+ it('sorts history in descending order by createDate and filters out DRAFT', async () => {
+ const history = [
+ {
+ status: { status: COMPLIANCE_REPORT_STATUSES.DRAFT },
+ createDate: '2024-10-02',
+ userProfile: { firstName: 'Draft', lastName: 'User' }
+ },
+ {
+ status: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED },
+ createDate: '2024-10-01T10:00:00Z',
+ userProfile: { firstName: 'John', lastName: 'Doe' }
+ },
+ {
+ status: { status: COMPLIANCE_REPORT_STATUSES.RECOMMENDED_BY_ANALYST },
+ createDate: '2024-10-03T15:00:00Z',
+ userProfile: { firstName: 'Jane', lastName: 'Smith' }
+ }
+ ]
+ renderComponent({
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED },
+ history
+ })
+
+ // SUBMITTED first because 2024-10-03 is later than 2024-10-01
+ await waitFor(() => {
+ const items = screen.getAllByTestId('list-item')
+ expect(items.length).toBe(2) // DRAFT is filtered out
+ // The first item should be RECOMMENDED_BY_ANALYST (2024-10-03)
+ expect(items[0].textContent).toContain(
+ 'report:complianceReportHistory.Recommended by analyst: Jane Smith'
+ )
+ // The second item should be SUBMITTED (2024-10-01)
+ expect(items[1].textContent).toContain(
+ 'report:complianceReportHistory.Submitted: John Doe'
+ )
+ })
+ })
+
+ it('replaces ASSESSED with AssessedBy if user is not government', async () => {
+ const history = [
+ {
+ status: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED },
+ createDate: '2024-10-01T10:00:00Z',
+ userProfile: { firstName: 'John', lastName: 'Doe' }
+ }
+ ]
+ renderComponent({
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED },
+ history
+ })
+
+ await waitFor(() => {
+ const item = screen.getByTestId('list-item')
+ // Should have replaced ASSESSED with AssessedBy
+ expect(item.textContent).toContain(
+ 'report:complianceReportHistory.AssessedBy: John Doe - formatted-2024-10-01T10:00:00Z'
+ )
+ })
+ })
+
+ it('does not replace ASSESSED with AssessedBy if user is government', async () => {
+ useCurrentUserHook.useCurrentUser.mockReturnValueOnce({
+ data: { isGovernmentUser: true },
+ isLoading: false
+ })
+ const history = [
+ {
+ status: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED },
+ createDate: '2024-10-01T10:00:00Z',
+ userProfile: { firstName: 'John', lastName: 'Doe' }
+ }
+ ]
+ renderComponent({
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED },
+ history
+ })
+
+ await waitFor(() => {
+ const item = screen.getByTestId('list-item')
+ // Should NOT have replaced ASSESSED with AssessedBy
+ expect(item.textContent).toContain(
+ 'report:complianceReportHistory.Assessed: John Doe - formatted-2024-10-01T10:00:00Z'
+ )
+ })
+ })
+})
diff --git a/frontend/src/views/ComplianceReports/__tests__/ViewLegacyComplianceReport.test.jsx b/frontend/src/views/ComplianceReports/__tests__/ViewLegacyComplianceReport.test.jsx
new file mode 100644
index 000000000..4394e044d
--- /dev/null
+++ b/frontend/src/views/ComplianceReports/__tests__/ViewLegacyComplianceReport.test.jsx
@@ -0,0 +1,149 @@
+import React from 'react'
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { describe, it, expect, beforeEach, vi } from 'vitest'
+import { COMPLIANCE_REPORT_STATUSES } from '@/constants/statuses'
+import { CONFIG } from '@/constants/config'
+import { LegacyAssessmentCard } from '@/views/ComplianceReports/components/LegacyAssessmentCard.jsx'
+import { wrapper } from '@/tests/utils/wrapper.jsx'
+
+// Mock useTranslation
+vi.mock('react-i18next', () => ({
+ useTranslation: () => ({
+ t: (key) => key
+ })
+}))
+
+// Mock HistoryCard
+vi.mock('@/views/ComplianceReports/components/HistoryCard.jsx', () => ({
+ HistoryCard: ({ report }) => HistoryCard - Version {report.version}
+}))
+
+// Mock window.open
+global.open = vi.fn()
+
+describe('LegacyAssessmentCard', () => {
+ const setup = (overrides = {}) => {
+ const defaultProps = {
+ orgData: {
+ name: 'Test Org',
+ orgAddress: {
+ addressLine1: '123 Test St',
+ city: 'Test City',
+ state: 'TS',
+ postalCode: '12345'
+ },
+ orgAttorneyAddress: {
+ addressLine1: '456 Law St',
+ city: 'Law City',
+ state: 'LS',
+ postalCode: '67890'
+ }
+ },
+ hasSupplemental: false,
+ isGovernmentUser: false,
+ currentStatus: COMPLIANCE_REPORT_STATUSES.DRAFT,
+ legacyReportId: '999',
+ chain: []
+ }
+
+ const props = { ...defaultProps, ...overrides }
+
+ return render(, { wrapper })
+ }
+
+ beforeEach(() => {
+ vi.resetAllMocks()
+ })
+
+ it('renders the organization details and addresses', () => {
+ setup()
+ expect(screen.getByText('report:orgDetails')).toBeInTheDocument()
+ expect(screen.getByText('Test Org')).toBeInTheDocument()
+ expect(screen.getByText(/report:serviceAddrLabel/)).toBeInTheDocument()
+ expect(screen.getByText(/report:bcAddrLabel/)).toBeInTheDocument()
+ })
+
+ it('displays contact instructions for non-government users', () => {
+ setup({ isGovernmentUser: false })
+ expect(
+ screen.getByText('report:contactForAddrChange', { exact: false })
+ ).toBeInTheDocument()
+ })
+
+ it('does not display contact instructions for government users', () => {
+ setup({ isGovernmentUser: true })
+ expect(
+ screen.queryByText('report:contactForAddrChange', { exact: false })
+ ).not.toBeInTheDocument()
+ })
+
+ it('displays the supplemental warning text', () => {
+ setup()
+ expect(screen.getByText('report:supplementalWarning')).toBeInTheDocument()
+ })
+
+ it('displays "View Legacy" button which opens the legacy report link on click', async () => {
+ setup({ legacyReportId: '999' })
+ const viewBtn = screen.getByText('report:viewLegacyBtn')
+ expect(viewBtn).toBeInTheDocument()
+ fireEvent.click(viewBtn)
+ await waitFor(() => {
+ expect(global.open).toHaveBeenCalledWith(
+ `${CONFIG.TFRS_BASE}/compliance_reporting/edit/999/intro`,
+ '_blank'
+ )
+ })
+ })
+
+ it('displays history when chain is not empty', () => {
+ setup({
+ chain: [
+ {
+ version: 0,
+ report: {
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.SUBMITTED }
+ }
+ },
+ {
+ version: 1,
+ report: {
+ currentStatus: { status: COMPLIANCE_REPORT_STATUSES.ASSESSED }
+ }
+ }
+ ]
+ })
+ expect(screen.getByText('report:reportHistory')).toBeInTheDocument()
+ expect(screen.getByText('HistoryCard - Version 0')).toBeInTheDocument()
+ expect(screen.getByText('HistoryCard - Version 1')).toBeInTheDocument()
+ })
+
+ it('shows "report:assessment" title when currentStatus is ASSESSED', () => {
+ setup({ currentStatus: COMPLIANCE_REPORT_STATUSES.ASSESSED })
+ expect(screen.getByText('report:assessment')).toBeInTheDocument()
+ })
+
+ it('shows "report:assessment" title for government users even if not assessed', () => {
+ setup({
+ isGovernmentUser: true,
+ currentStatus: COMPLIANCE_REPORT_STATUSES.DRAFT
+ })
+ expect(screen.getByText('report:assessment')).toBeInTheDocument()
+ })
+
+ it('shows "report:assessment" title if supplemental is true', () => {
+ setup({
+ hasSupplemental: true,
+ currentStatus: COMPLIANCE_REPORT_STATUSES.DRAFT
+ })
+ expect(screen.getByText('report:assessment')).toBeInTheDocument()
+ })
+
+ it('shows "report:orgDetails" title if not assessed, not government user, and no supplemental', () => {
+ setup({
+ isGovernmentUser: false,
+ hasSupplemental: false,
+ currentStatus: COMPLIANCE_REPORT_STATUSES.DRAFT
+ })
+ expect(screen.getByText('report:orgDetails')).toBeInTheDocument()
+ })
+})
diff --git a/frontend/src/views/ComplianceReports/components/AssessmentCard.jsx b/frontend/src/views/ComplianceReports/components/AssessmentCard.jsx
index 1b69bcd17..9c55d4117 100644
--- a/frontend/src/views/ComplianceReports/components/AssessmentCard.jsx
+++ b/frontend/src/views/ComplianceReports/components/AssessmentCard.jsx
@@ -6,119 +6,14 @@ import { StyledListItem } from '@/components/StyledListItem'
import { roles } from '@/constants/roles'
import { COMPLIANCE_REPORT_STATUSES } from '@/constants/statuses'
import { useCreateSupplementalReport } from '@/hooks/useComplianceReports'
-import { useCurrentUser } from '@/hooks/useCurrentUser'
import { constructAddress } from '@/utils/constructAddress'
-import { timezoneFormatter } from '@/utils/formatters'
import AssignmentIcon from '@mui/icons-material/Assignment'
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
-import { List, ListItemText, Stack, styled } from '@mui/material'
-import MuiAccordion from '@mui/material/Accordion'
-import MuiAccordionDetails from '@mui/material/AccordionDetails'
-import MuiAccordionSummary, {
- accordionSummaryClasses
-} from '@mui/material/AccordionSummary'
+import { List, ListItemText, Stack } from '@mui/material'
import Box from '@mui/material/Box'
-import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useNavigate } from 'react-router-dom'
import { FEATURE_FLAGS, isFeatureEnabled } from '@/constants/config.js'
-
-const Accordion = styled((props) => (
-
-))(() => ({
- border: `none`,
- '&::before': {
- display: 'none'
- }
-}))
-
-const AccordionSummary = styled((props) => (
- }
- {...props}
- />
-))(() => ({
- minHeight: 'unset',
- padding: 0,
- flexDirection: 'row-reverse',
- [`& .${accordionSummaryClasses.content}`]: {
- margin: 0
- },
- [`& .${accordionSummaryClasses.expanded}`]: {
- margin: 0
- }
-}))
-
-const AccordionDetails = styled(MuiAccordionDetails)(() => ({
- paddingLeft: '1rem',
- paddingTop: 0,
- paddingBottom: 0
-}))
-
-const HistoryCard = ({ report }) => {
- const { data: currentUser } = useCurrentUser()
- const isGovernmentUser = currentUser?.isGovernmentUser
- const { t } = useTranslation(['report'])
- const filteredHistory = useMemo(() => {
- if (!report.history || report.history.length === 0) {
- return []
- }
- // Sort the history array by date in descending order
- return [...report.history]
- .sort((a, b) => {
- return new Date(b.createDate) - new Date(a.createDate)
- })
- .map((item) => {
- if (
- item.status.status === COMPLIANCE_REPORT_STATUSES.ASSESSED &&
- !isGovernmentUser
- ) {
- item.status.status = 'AssessedBy'
- }
- return item
- })
- .filter((item) => item.status.status !== COMPLIANCE_REPORT_STATUSES.DRAFT)
- }, [isGovernmentUser, report.history])
- return (
-
- }
- aria-controls="panel1-content"
- >
-
- {report.version === 0
- ? `${report.compliancePeriod.description} Compliance Report`
- : report.nickname}
- : {report.currentStatus.status}
-
-
-
-
- {filteredHistory.map((item, index) => (
-
-
-
-
-
- ))}
-
-
-
- )
-}
+import { HistoryCard } from '@/views/ComplianceReports/components/HistoryCard.jsx'
export const AssessmentCard = ({
orgData,
diff --git a/frontend/src/views/ComplianceReports/components/HistoryCard.jsx b/frontend/src/views/ComplianceReports/components/HistoryCard.jsx
new file mode 100644
index 000000000..fb8a755a8
--- /dev/null
+++ b/frontend/src/views/ComplianceReports/components/HistoryCard.jsx
@@ -0,0 +1,114 @@
+import React, { useMemo } from 'react'
+import { List, ListItemText, styled } from '@mui/material'
+import MuiAccordion from '@mui/material/Accordion'
+import MuiAccordionSummary, {
+ accordionSummaryClasses
+} from '@mui/material/AccordionSummary'
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
+import MuiAccordionDetails from '@mui/material/AccordionDetails'
+import { useCurrentUser } from '@/hooks/useCurrentUser.js'
+import { useTranslation } from 'react-i18next'
+import { COMPLIANCE_REPORT_STATUSES } from '@/constants/statuses.js'
+import BCTypography from '@/components/BCTypography/index.jsx'
+import { StyledListItem } from '@/components/StyledListItem.jsx'
+import { timezoneFormatter } from '@/utils/formatters.js'
+
+const Accordion = styled((props) => (
+
+))(() => ({
+ border: `none`,
+ '&::before': {
+ display: 'none'
+ }
+}))
+
+const AccordionSummary = styled((props) => (
+ }
+ {...props}
+ />
+))(() => ({
+ minHeight: 'unset',
+ padding: 0,
+ flexDirection: 'row-reverse',
+ [`& .${accordionSummaryClasses.content}`]: {
+ margin: 0
+ },
+ [`& .${accordionSummaryClasses.expanded}`]: {
+ margin: 0
+ }
+}))
+
+const AccordionDetails = styled(MuiAccordionDetails)(() => ({
+ paddingLeft: '1rem',
+ paddingTop: 0,
+ paddingBottom: 0
+}))
+
+export const HistoryCard = ({ report }) => {
+ const { data: currentUser } = useCurrentUser()
+ const isGovernmentUser = currentUser?.isGovernmentUser
+ const { t } = useTranslation(['report'])
+ const filteredHistory = useMemo(() => {
+ if (!report.history || report.history.length === 0) {
+ return []
+ }
+ // Sort the history array by date in descending order
+ return [...report.history]
+ .sort((a, b) => {
+ return new Date(b.createDate) - new Date(a.createDate)
+ })
+ .map((item) => {
+ if (
+ item.status.status === COMPLIANCE_REPORT_STATUSES.ASSESSED &&
+ !isGovernmentUser
+ ) {
+ item.status.status = 'AssessedBy'
+ }
+ return item
+ })
+ .filter((item) => item.status.status !== COMPLIANCE_REPORT_STATUSES.DRAFT)
+ }, [isGovernmentUser, report.history])
+ return (
+
+ }
+ aria-controls="panel1-content"
+ >
+
+ {report.version === 0
+ ? `${report.compliancePeriod.description} Compliance Report`
+ : report.nickname}
+ : {report.currentStatus.status}
+
+
+
+
+ {filteredHistory.map((item, index) => (
+
+
+
+
+
+ ))}
+
+
+
+ )
+}
diff --git a/frontend/src/views/ComplianceReports/components/LegacyAssessmentCard.jsx b/frontend/src/views/ComplianceReports/components/LegacyAssessmentCard.jsx
new file mode 100644
index 000000000..697a17b4f
--- /dev/null
+++ b/frontend/src/views/ComplianceReports/components/LegacyAssessmentCard.jsx
@@ -0,0 +1,128 @@
+import BCButton from '@/components/BCButton'
+import BCTypography from '@/components/BCTypography'
+import BCWidgetCard from '@/components/BCWidgetCard/BCWidgetCard'
+import { StyledListItem } from '@/components/StyledListItem'
+import { COMPLIANCE_REPORT_STATUSES } from '@/constants/statuses'
+import { constructAddress } from '@/utils/constructAddress'
+import AssignmentIcon from '@mui/icons-material/Assignment'
+import { List, ListItemText, Stack } from '@mui/material'
+import Box from '@mui/material/Box'
+import { useTranslation } from 'react-i18next'
+import { CONFIG } from '@/constants/config'
+import { HistoryCard } from '@/views/ComplianceReports/components/HistoryCard.jsx'
+
+export const LegacyAssessmentCard = ({
+ orgData,
+ hasSupplemental,
+ isGovernmentUser,
+ currentStatus,
+ legacyReportId,
+ chain
+}) => {
+ const { t } = useTranslation(['report'])
+
+ const viewLegacyReport = () => {
+ window.open(
+ `${CONFIG.TFRS_BASE}/compliance_reporting/edit/${legacyReportId}/intro`,
+ '_blank'
+ )
+ }
+ return (
+ <>
+
+
+
+ {orgData?.name}
+
+
+
+
+ {t('report:serviceAddrLabel')}:
+ {orgData && constructAddress(orgData.orgAddress)}
+
+
+
+
+ {t('report:bcAddrLabel')}:{' '}
+ {orgData && constructAddress(orgData.orgAttorneyAddress)}
+
+
+
+
+ {!isGovernmentUser && (
+
+ )}
+ {!!chain.length && (
+ <>
+
+ {t('report:reportHistory')}
+
+ {chain.map((report) => (
+
+ ))}
+ >
+ )}
+
+
+ {t('report:supplementalWarning')}
+
+
+ }
+ sx={{ mt: 2 }}
+ onClick={viewLegacyReport}
+ >
+ {t('report:viewLegacyBtn')}
+
+
+
+ >
+ }
+ />
+
+ {t('report:questions')}
+
+
+ >
+ )
+}
diff --git a/frontend/src/views/ComplianceReports/components/ReportDetails.jsx b/frontend/src/views/ComplianceReports/components/ReportDetails.jsx
index 351110c15..f14545f29 100644
--- a/frontend/src/views/ComplianceReports/components/ReportDetails.jsx
+++ b/frontend/src/views/ComplianceReports/components/ReportDetails.jsx
@@ -32,13 +32,20 @@ import { FuelExportSummary } from '@/views/FuelExports/FuelExportSummary'
import { SupportingDocumentSummary } from '@/views/SupportingDocuments/SupportingDocumentSummary'
import DocumentUploadDialog from '@/components/Documents/DocumentUploadDialog'
import { useComplianceReportDocuments } from '@/hooks/useComplianceReports'
+import { COMPLIANCE_REPORT_STATUSES } from '@/constants/statuses'
-const ReportDetails = ({ currentStatus = 'Draft' }) => {
+const ReportDetails = ({ currentStatus = 'Draft', isAnalystRole }) => {
const { t } = useTranslation()
const { compliancePeriod, complianceReportId } = useParams()
const navigate = useNavigate()
const [isFileDialogOpen, setFileDialogOpen] = useState(false)
+ const editSupportingDocs = useMemo(() => {
+ return isAnalystRole && (
+ currentStatus === COMPLIANCE_REPORT_STATUSES.SUBMITTED ||
+ currentStatus === COMPLIANCE_REPORT_STATUSES.ASSESSED
+ ) || currentStatus === COMPLIANCE_REPORT_STATUSES.DRAFT;
+ }, [isAnalystRole, currentStatus]);
const isArrayEmpty = useCallback((data) => {
if (Array.isArray(data)) {
@@ -64,11 +71,22 @@ const ReportDetails = ({ currentStatus = 'Draft' }) => {
},
useFetch: useComplianceReportDocuments,
component: (data) => (
-
- )
+ <>
+
+ {
+ setFileDialogOpen(false)
+ }}
+ />
+ >
+ ),
+ condition: true
},
{
name: t('report:activityLists.supplyOfFuel'),
@@ -171,9 +189,13 @@ const ReportDetails = ({ currentStatus = 'Draft' }) => {
]
)
- const [expanded, setExpanded] = useState(() =>
- activityList.map((_, index) => `panel${index}`)
- )
+ const [expanded, setExpanded] = useState(activityList.map((activity, index) => {
+ if (activity.name === t('report:supportingDocs')) {
+ return isArrayEmpty(activity.useFetch(complianceReportId).data) ? '' : `panel${index}`
+ }
+ return `panel${index}`
+ }).filter(Boolean)) // Initialize with panels that should be open by default
+
const [allExpanded, setAllExpanded] = useState(true)
const handleChange = (panel) => (event, isExpanded) => {
@@ -217,11 +239,10 @@ const ReportDetails = ({ currentStatus = 'Draft' }) => {
{activityList.map((activity, index) => {
const { data, error, isLoading } = activity.useFetch(complianceReportId)
return (
- data &&
- !isArrayEmpty(data) && (
+ (data && !isArrayEmpty(data) || activity.name === t('report:supportingDocs')) && (
{
component="div"
>
{activity.name}
- {currentStatus === 'Draft' && (
+ {editSupportingDocs && (
<>
diff --git a/frontend/src/views/ComplianceReports/components/_schema.jsx b/frontend/src/views/ComplianceReports/components/_schema.jsx
index e83fcdc19..e7f1546fb 100644
--- a/frontend/src/views/ComplianceReports/components/_schema.jsx
+++ b/frontend/src/views/ComplianceReports/components/_schema.jsx
@@ -1,6 +1,6 @@
import { BCColumnSetFilter } from '@/components/BCDataGrid/components'
import { SUMMARY } from '@/constants/common'
-import { ReportsStatusRenderer, LinkRenderer } from '@/utils/grid/cellRenderers'
+import { ReportsStatusRenderer } from '@/utils/grid/cellRenderers'
import { timezoneFormatter } from '@/utils/formatters'
export const reportsColDefs = (t, bceidRole) => [
@@ -8,11 +8,6 @@ export const reportsColDefs = (t, bceidRole) => [
field: 'compliancePeriod',
headerName: t('report:reportColLabels.compliancePeriod'),
width: 210,
- cellRenderer: LinkRenderer,
- cellRendererParams: {
- url: ({ data }) =>
- `${data.compliancePeriod?.description}/${data.complianceReportId}`
- },
valueGetter: ({ data }) => data.compliancePeriod?.description || '',
filterParams: {
buttons: ['clear']
@@ -23,11 +18,6 @@ export const reportsColDefs = (t, bceidRole) => [
headerName: t('report:reportColLabels.organization'),
flex: 2,
hide: bceidRole,
- cellRenderer: LinkRenderer,
- cellRendererParams: {
- url: ({ data }) =>
- `${data.compliancePeriod?.description}/${data.complianceReportId}`
- },
valueGetter: ({ data }) => data.organization?.name || ''
},
{
diff --git a/frontend/src/views/FuelCodes/AddFuelCode/AddEditFuelCode.jsx b/frontend/src/views/FuelCodes/AddFuelCode/AddEditFuelCode.jsx
index ca05f1413..1e54f01bc 100644
--- a/frontend/src/views/FuelCodes/AddFuelCode/AddEditFuelCode.jsx
+++ b/frontend/src/views/FuelCodes/AddFuelCode/AddEditFuelCode.jsx
@@ -103,12 +103,14 @@ const AddEditFuelCodeBase = () => {
if (existingFuelCode) {
const transformedData = {
...existingFuelCode,
- feedstockFuelTransportMode: existingFuelCode.feedstockFuelTransportModes.map(
- (mode) => mode.feedstockFuelTransportMode.transportMode
- ),
- finishedFuelTransportMode: existingFuelCode.finishedFuelTransportModes.map(
- (mode) => mode.finishedFuelTransportMode.transportMode
- )
+ feedstockFuelTransportMode:
+ existingFuelCode.feedstockFuelTransportModes.map(
+ (mode) => mode.feedstockFuelTransportMode.transportMode
+ ),
+ finishedFuelTransportMode:
+ existingFuelCode.finishedFuelTransportModes.map(
+ (mode) => mode.finishedFuelTransportMode.transportMode
+ )
}
setRowData([transformedData])
} else {
@@ -452,6 +454,12 @@ const AddEditFuelCodeBase = () => {
{existingFuelCode?.fuelCodeStatus.status ===
FUEL_CODE_STATUSES.APPROVED && t('fuelCode:viewFuelCodeTitle')}
+
+ {(!existingFuelCode ||
+ existingFuelCode?.fuelCodeStatus.status ===
+ FUEL_CODE_STATUSES.DRAFT) &&
+ t('fuelCode:fuelCodeEntryGuide')}
+