# Tasks ## Task 1: Database Migration — Add `dismissed_at` Column - [x] 1.1 Create migration file `backend/migrations/add_fp_submissions_dismissed.js` that adds a `dismissed_at TIMESTAMPTZ DEFAULT NULL` column to the `ivanti_fp_submissions` table using `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` - [x] 1.2 Run the migration and verify the column exists in the database schema ## Task 2: Backend — Dismiss Endpoint - [x] 2.1 Add `PATCH /submissions/:id/dismiss` endpoint to `backend/routes/ivantiFpWorkflow.js` with `requireAuth()` and `requireGroup('Admin', 'Standard_User')` middleware - [x] 2.2 Implement ownership verification (user_id match, return 403 if mismatch) - [x] 2.3 Implement lifecycle guard (only `lifecycle_status === 'rejected'` can be dismissed, return 400 otherwise) - [x] 2.4 Set `dismissed_at = NOW()` on the submission record on success - [x] 2.5 Log audit entry with action `ivanti_fp_submission_dismissed`, entity_type `ivanti_workflow`, and the workflow batch ID as entity_id ## Task 3: Backend — Pure Filter Function - [x] 3.1 Create and export `filterVisibleSubmissions(submissions)` function in `backend/routes/ivantiFpWorkflow.js` that excludes submissions with `lifecycle_status === 'approved'` or `dismissed_at !== null` - [x] 3.2 Create and export `shouldShowDismissButton(submission)` predicate that returns true only when `lifecycle_status === 'rejected'` and `dismissed_at` is null ## Task 4: Frontend — Filter Approved and Dismissed Submissions - [x] 4.1 Modify the `fpSubmissions` useMemo in `ReportingPage.js` to filter out submissions where `lifecycle_status === 'approved'` or `dismissed_at` is not null before passing to the QueuePanel - [x] 4.2 Verify that the FpEditModal can still be opened for approved submissions from Reporting Table workflow badges (no filtering on that path) ## Task 5: Frontend — Dismiss Button on Rejected Submissions - [x] 5.1 Add an X button (lucide `X` icon, 12px) to the right side of submission rows where `lifecycle_status === 'rejected'` in the QueuePanel submissions section - [x] 5.2 Implement click handler that calls `PATCH /api/ivanti/fp-workflow/submissions/:id/dismiss` and removes the submission from the visible list on success - [x] 5.3 Use `e.stopPropagation()` on the X button to prevent triggering the row's click-to-edit handler - [x] 5.4 Show error feedback if the dismiss API call fails ## Task 6: Frontend — Collapsible Submissions Section - [x] 6.1 Add `submissionsCollapsed` state initialized from `localStorage.getItem('steam_submissions_collapsed')`, defaulting to `false` (expanded) - [x] 6.2 Add a clickable chevron icon (ChevronDown when expanded, ChevronUp when collapsed) to the SUBMISSIONS header row - [x] 6.3 Conditionally render the submissions list based on collapsed state — when collapsed, hide the list but keep the header with the count badge visible - [x] 6.4 Persist collapsed/expanded state to localStorage on toggle using key `steam_submissions_collapsed` ## Task 7: Property-Based Tests - [x] 7.1 Create `backend/__tests__/fp-submissions-cleanup.property.test.js` with fast-check - [x] 7.2 Implement Property 1 test: for any array of submissions with random lifecycle_status and dismissed_at values, `filterVisibleSubmissions` returns only non-approved and non-dismissed submissions (min 100 iterations) - [x] 7.3 Implement Property 2 test: for any submission with random lifecycle_status and dismissed_at, `shouldShowDismissButton` returns true iff status is 'rejected' and dismissed_at is null (min 100 iterations) ## Task 8: Unit and Integration Tests - [x] 8.1 Write unit tests for the dismiss endpoint (happy path, wrong status, ownership check, not found) - [x] 8.2 Write unit tests for filter edge cases (all approved, all dismissed, mixed, empty array) - [x] 8.3 Write integration test verifying dismissed submissions remain in the database but are excluded from the filtered list