# Queue Collapsible Sections — Design
## Overview
This feature adds collapsible, grouped sections to the IvantiTodoQueuePage. Items are organized into a hybrid layout: an "Inventory" section (CARD, GRANITE, DECOM workflow types) at the top, followed by vendor-grouped sections for FP and Archer items. Each section has a clickable header that toggles visibility of its items. The implementation is entirely frontend — no backend or database changes are needed.
## Architecture
The feature modifies a single file: `frontend/src/components/pages/IvantiTodoQueuePage.js`. It introduces:
1. A **grouping computation** (via `useMemo`) that transforms the flat `visibleItems` array into a structured array of sections.
2. A **collapse state** (via `useState`) that tracks which sections are expanded or collapsed.
3. **Section header components** rendered inline that display group labels, item counts, and chevron icons.
4. **Extended styles** added to the existing `STYLES` constant.
No new files, components, or dependencies are introduced. The existing selection logic, floating action bar, consolidation modal, and ticket link badges continue to operate unchanged.
## Components and Data Flow
```
queueItems (from API)
│
▼
visibleItems = useMemo(filter pending) ← existing
│
▼
groupedSections = useMemo(groupItems) ← NEW: hybrid grouping logic
│
▼
collapsedSections (useState) ← NEW: collapse state
│
▼
Render: map over groupedSections
├── Section Header (clickable, toggles collapse)
└── Section Body (conditionally rendered based on collapse state)
└── Queue Item Rows (existing rendering logic)
```
## Data Model
### Grouped Section Structure
The `useMemo` grouping hook produces an array of section objects:
```javascript
// Output of the grouping computation
const groupedSections = [
{
key: 'inventory', // Unique section identifier
label: 'Inventory', // Display label
type: 'inventory', // Section type for styling
items: [/* queue items */], // Items belonging to this section
},
{
key: 'vendor:Microsoft', // Unique section identifier
label: 'Microsoft', // Display label (vendor name)
type: 'vendor', // Section type for styling
items: [/* queue items */], // Items belonging to this section
},
// ... more vendor sections, alphabetically sorted
];
```
### Collapse State
```javascript
// Key: section.key string, Value: boolean (true = collapsed)
const [collapsedSections, setCollapsedSections] = useState({});
// Initial state: empty object → all sections default to expanded
```
## Interfaces
### Grouping Function
```javascript
/**
* Groups visible queue items into the hybrid section layout.
*
* @param {Array} visibleItems - Queue items with status 'pending'
* @returns {Array<{key: string, label: string, type: string, items: Array}>}
*
* Rules:
* - Items with workflow_type CARD, GRANITE, or DECOM → Inventory section
* - Items with workflow_type FP or Archer → grouped by vendor field
* - Items with null/undefined/empty vendor → placed in "Unknown" vendor section
* - Inventory section appears first (if non-empty)
* - Vendor sections sorted alphabetically by label
* - Sections with zero items are omitted from output
*/
const groupedSections = useMemo(() => {
const INVENTORY_TYPES = new Set(['CARD', 'GRANITE', 'DECOM']);
const inventoryItems = [];
const vendorMap = new Map(); // vendor name → items array
for (const item of visibleItems) {
if (INVENTORY_TYPES.has(item.workflow_type)) {
inventoryItems.push(item);
} else {
const vendor = item.vendor?.trim() || 'Unknown';
if (!vendorMap.has(vendor)) vendorMap.set(vendor, []);
vendorMap.get(vendor).push(item);
}
}
const sections = [];
// Inventory section first (only if non-empty)
if (inventoryItems.length > 0) {
sections.push({
key: 'inventory',
label: 'Inventory',
type: 'inventory',
items: inventoryItems,
});
}
// Vendor sections sorted alphabetically
const sortedVendors = [...vendorMap.entries()]
.sort((a, b) => a[0].localeCompare(b[0]));
for (const [vendor, items] of sortedVendors) {
sections.push({
key: `vendor:${vendor}`,
label: vendor,
type: 'vendor',
items,
});
}
return sections;
}, [visibleItems]);
```
### Toggle Collapse Handler
```javascript
const toggleSection = useCallback((sectionKey) => {
setCollapsedSections((prev) => ({
...prev,
[sectionKey]: !prev[sectionKey],
}));
}, []);
```
### Section Header Rendering
```javascript
// Within the render, for each section:
{groupedSections.map((section) => {
const isCollapsed = !!collapsedSections[section.key];
return (
{/* Section Header */}
toggleSection(section.key)}
style={section.type === 'inventory'
? STYLES.sectionHeaderInventory
: STYLES.sectionHeaderVendor}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggleSection(section.key);
}
}}
aria-expanded={!isCollapsed}
aria-label={`${section.label} section, ${section.items.length} items`}
>
{isCollapsed
?
:
}
{section.label}
({section.items.length})
{/* Section Body — only rendered when expanded */}
{!isCollapsed && section.items.map((item) => (
// ... existing queue item row rendering
))}
);
})}
```
## Styling
New entries added to the existing `STYLES` constant:
```javascript
sectionHeaderInventory: {
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.5rem 0.75rem',
marginTop: '0.5rem',
borderBottom: '1px solid rgba(16, 185, 129, 0.2)',
cursor: 'pointer',
userSelect: 'none',
fontFamily: 'monospace',
fontSize: '0.7rem',
fontWeight: 700,
color: '#10B981',
textTransform: 'uppercase',
letterSpacing: '0.1em',
},
sectionHeaderVendor: {
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.5rem 0.75rem',
marginTop: '0.5rem',
borderBottom: '1px solid rgba(148, 163, 184, 0.15)',
cursor: 'pointer',
userSelect: 'none',
fontFamily: 'monospace',
fontSize: '0.7rem',
fontWeight: 700,
color: '#94A3B8',
textTransform: 'uppercase',
letterSpacing: '0.1em',
},
sectionCount: {
fontFamily: 'monospace',
fontSize: '0.65rem',
fontWeight: 600,
color: '#64748B',
marginLeft: '0.25rem',
},
```
## Icon Imports
Add `ChevronDown` and `ChevronRight` to the existing lucide-react import:
```javascript
import { ListTodo, RefreshCw, CheckSquare, Square, Loader, AlertCircle, X, Plus, CheckCircle, ChevronDown, ChevronRight } from 'lucide-react';
```
## Select All Interaction with Collapsed Sections
The existing `toggleSelectAll` and `allVisibleSelected` logic operates on the `visibleItems` array, which contains ALL pending items regardless of collapse state. This means:
- **Select All** selects/deselects all pending items across all sections, whether those sections are collapsed or expanded.
- **Selection count** always reflects `selectedIds.size`, which includes items in collapsed sections.
- **Floating action bar** operates on `selectedQueueItems` (derived from `selectedIds`), which is independent of collapse state.
No changes to the selection logic are needed. The collapse state is purely visual — it controls rendering, not data.
## Error Handling
- If `visibleItems` is empty, `groupedSections` will be an empty array, and the existing empty state renders instead.
- If an item has an unexpected `workflow_type` (not CARD, GRANITE, DECOM, FP, or Archer), it falls into the vendor grouping path and is grouped by its vendor field. This is a safe fallback.
- The `collapsedSections` state uses an object with string keys. Non-existent keys return `undefined`, which is falsy, so all sections default to expanded without explicit initialization.
## Performance Considerations
- The `useMemo` grouping computation runs only when `visibleItems` changes (on fetch or status update). For typical queue sizes (10–100 items), this is negligible.
- Collapse state changes trigger re-renders only of the affected section's body. React's reconciliation handles this efficiently since each section has a stable `key`.
- No additional API calls or data fetching is introduced.
## Correctness Properties
*A property is a characteristic or behavior that should hold true across all valid executions of a system — essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
### Property 1: Grouping Correctness
For any array of visible queue items, every item with workflow_type CARD, GRANITE, or DECOM SHALL appear in the Inventory section, every item with workflow_type FP or Archer SHALL appear in the vendor section matching its vendor field (or "Unknown" if vendor is null/empty), and no item SHALL appear in more than one section.
**Validates: Requirements 1.1, 1.2, 1.5**
### Property 2: Section Ordering
For any non-empty grouping result, the Inventory section (if present) SHALL be the first element, and all subsequent vendor sections SHALL be sorted in case-insensitive alphabetical order by their label.
**Validates: Requirements 1.3, 1.4**
### Property 3: Empty Section Omission
For any array of visible queue items, the grouped output SHALL contain no sections with zero items. Specifically, if no CARD/GRANITE/DECOM items exist, no Inventory section appears; if no items exist for a given vendor, no section for that vendor appears.
**Validates: Requirements 1.6, 1.7**
### Property 4: Section Header Count Accuracy
For any section in the grouped output, the item count displayed in the section header SHALL equal the actual number of items in that section's items array.
**Validates: Requirements 3.1, 3.2**
### Property 5: Selection Independence from Collapse State
For any combination of selected items and collapse state, the set of selected item IDs SHALL remain unchanged when sections are collapsed or expanded. The selection count SHALL always equal the total number of selected items across all sections, and Select All SHALL toggle selection for all visible items regardless of which sections are collapsed.
**Validates: Requirements 4.2, 4.4, 4.5**