tenantpilot/components/policy-explorer/PolicyTableToolbar.tsx
Ahmed Darrazi 4288bc7884
All checks were successful
Trigger Cloudarix Deploy / call-webhook (push) Successful in 2s
feat(policy-explorer-v2): implement Phase 5 - Bulk CSV Export
 New Features (Tasks T029-T037)
- Client-side CSV export for selected rows
- Server-side CSV export for all filtered results (max 5000)
- RFC 4180 compliant CSV formatting with proper escaping
- UTF-8 BOM for Excel compatibility
- ExportButton dropdown with two export modes
- Warning UI when results exceed 5000 rows
- Loading state with spinner during server export

📦 New Files
- lib/utils/csv-export.ts - CSV generation utilities
- components/policy-explorer/ExportButton.tsx - Export dropdown

🔧 Updates
- PolicyTableToolbar now includes ExportButton
- PolicyExplorerV2Client passes export props
- Filename generation with timestamp and row count

 Zero TypeScript compilation errors
 All Phase 5 tasks complete (T029-T037)
 Ready for Phase 6 (Enhanced Detail View)

Refs: specs/004-policy-explorer-v2/tasks.md Phase 5
2025-12-10 00:30:33 +01:00

150 lines
4.2 KiB
TypeScript

/**
* PolicyTableToolbar
*
* Toolbar above the data table with:
* - Column visibility menu
* - Density mode toggle (compact/comfortable)
* - Export button (added later in Phase 5)
* - Filter controls (added later in Phase 4)
*/
'use client';
import { Button } from '@/components/ui/button';
import { ColumnVisibilityMenu } from './ColumnVisibilityMenu';
import { PolicyTypeFilter } from './PolicyTypeFilter';
import { ExportButton } from './ExportButton';
import { LayoutList, LayoutGrid, X } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import type { Table } from '@tanstack/react-table';
import type { PolicySettingRow } from '@/lib/types/policy-table';
interface PolicyTableToolbarProps {
table: Table<PolicySettingRow>;
density: 'compact' | 'comfortable';
onDensityChange: (density: 'compact' | 'comfortable') => void;
// Filter props
selectedPolicyTypes: string[];
onSelectedPolicyTypesChange: (types: string[]) => void;
searchQuery: string;
onSearchQueryChange: (query: string) => void;
// Export props
selectedRows: PolicySettingRow[];
selectedCount: number;
totalCount: number;
sortBy?: string;
sortDir?: 'asc' | 'desc';
}
export function PolicyTableToolbar({
table,
density,
onDensityChange,
selectedPolicyTypes,
onSelectedPolicyTypesChange,
searchQuery,
onSearchQueryChange,
selectedRows,
selectedCount,
totalCount,
sortBy,
sortDir,
}: PolicyTableToolbarProps) {
const hasActiveFilters = selectedPolicyTypes.length > 0 || searchQuery.length > 0;
const handleClearFilters = () => {
onSelectedPolicyTypesChange([]);
onSearchQueryChange('');
};
return (
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
{/* Policy Type Filter */}
<PolicyTypeFilter
selectedTypes={selectedPolicyTypes}
onSelectedTypesChange={onSelectedPolicyTypesChange}
/>
{/* Active Filter Badges */}
{selectedPolicyTypes.length > 0 && (
<div className="flex items-center gap-1">
{selectedPolicyTypes.slice(0, 2).map((type) => (
<Badge
key={type}
variant="secondary"
className="h-6 px-2 text-xs"
>
{type}
<button
onClick={() =>
onSelectedPolicyTypesChange(
selectedPolicyTypes.filter((t) => t !== type)
)
}
className="ml-1 hover:text-destructive"
>
<X className="h-3 w-3" />
</button>
</Badge>
))}
{selectedPolicyTypes.length > 2 && (
<Badge variant="secondary" className="h-6 px-2 text-xs">
+{selectedPolicyTypes.length - 2} more
</Badge>
)}
</div>
)}
{/* Clear All Filters Button */}
{hasActiveFilters && (
<Button
variant="ghost"
size="sm"
onClick={handleClearFilters}
className="h-8 px-2 lg:px-3"
>
Clear filters
</Button>
)}
</div>
<div className="flex items-center space-x-2">
{/* Density Toggle */}
<Button
variant="outline"
size="sm"
className="h-8"
onClick={() => onDensityChange(density === 'compact' ? 'comfortable' : 'compact')}
>
{density === 'compact' ? (
<>
<LayoutGrid className="mr-2 h-4 w-4" />
Comfortable
</>
) : (
<>
<LayoutList className="mr-2 h-4 w-4" />
Compact
</>
)}
</Button>
{/* Column Visibility Menu */}
<ColumnVisibilityMenu table={table} />
{/* Export Button */}
<ExportButton
selectedRows={selectedRows}
selectedCount={selectedCount}
totalCount={totalCount}
policyTypes={selectedPolicyTypes}
searchQuery={searchQuery}
sortBy={sortBy}
sortDir={sortDir}
/>
</div>
</div>
);
}