Re_Figma_Code/src/pages/ClosedRequests/ClosedRequests.tsx

207 lines
7.9 KiB
TypeScript

import { useCallback, useRef, useEffect, useMemo } from 'react';
// Components
import { ClosedRequestsHeader } from './components/ClosedRequestsHeader';
import { ClosedRequestsList } from './components/ClosedRequestsList';
import { ClosedRequestsEmpty } from './components/ClosedRequestsEmpty';
import { ClosedRequestsPagination } from './components/ClosedRequestsPagination';
// Hooks
import { useClosedRequests } from './hooks/useClosedRequests';
import { useClosedRequestsFilters } from './hooks/useClosedRequestsFilters';
// Types
import type { ClosedRequestsProps } from './types/closedRequests.types';
// Utils & Factory
import { getUserFilterType } from '@/utils/userFilterUtils';
import { getClosedRequestsFilters } from '@/flows';
import { TokenManager } from '@/utils/tokenManager';
export function ClosedRequests({ onViewRequest }: ClosedRequestsProps) {
// Data fetching hook
const closedRequests = useClosedRequests({ itemsPerPage: 10 });
// Filters hook - use ref to avoid circular dependency
const fetchRef = useRef(closedRequests.fetchRequests);
fetchRef.current = closedRequests.fetchRequests;
const filters = useClosedRequestsFilters();
// Get user filter type and corresponding filter component (plug-and-play pattern)
const userFilterType = useMemo(() => {
try {
const userData = TokenManager.getUserData();
return getUserFilterType(userData);
} catch (error) {
console.error('[ClosedRequests] Error getting user filter type:', error);
return 'STANDARD' as const;
}
}, []);
// Get the appropriate filter component based on user type
const ClosedRequestsFiltersComponent = useMemo(() => {
return getClosedRequestsFilters(userFilterType);
}, [userFilterType]);
const isDealer = userFilterType === 'DEALER';
const prevFiltersRef = useRef({
searchTerm: filters.searchTerm,
statusFilter: filters.statusFilter,
priorityFilter: filters.priorityFilter,
templateTypeFilter: filters.templateTypeFilter,
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
});
const hasInitialFetchRun = useRef(false);
// Initial fetch on mount - use stored page from Redux
useEffect(() => {
const storedPage = filters.currentPage || 1;
fetchRef.current(storedPage, {
search: filters.searchTerm || undefined,
status: filters.statusFilter !== 'all' ? filters.statusFilter : undefined,
// Only include priority and templateType filters if user is not a dealer
priority: !isDealer && filters.priorityFilter !== 'all' ? filters.priorityFilter : undefined,
templateType: !isDealer && filters.templateTypeFilter !== 'all' ? filters.templateTypeFilter : undefined,
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
});
hasInitialFetchRun.current = true;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isDealer]); // Re-fetch if dealer status changes
// Track filter changes and refetch
useEffect(() => {
if (!hasInitialFetchRun.current) return;
const prev = prevFiltersRef.current;
const hasChanged =
prev.searchTerm !== filters.searchTerm ||
prev.statusFilter !== filters.statusFilter ||
prev.priorityFilter !== filters.priorityFilter ||
prev.templateTypeFilter !== filters.templateTypeFilter ||
prev.sortBy !== filters.sortBy ||
prev.sortOrder !== filters.sortOrder;
if (!hasChanged) return; // No actual change, skip
// Debounce search
const timeoutId = setTimeout(() => {
filters.setCurrentPage(1); // Reset to page 1 when filters change
fetchRef.current(1, {
search: filters.searchTerm || undefined,
status: filters.statusFilter !== 'all' ? filters.statusFilter : undefined,
priority: filters.priorityFilter !== 'all' ? filters.priorityFilter : undefined,
templateType: filters.templateTypeFilter !== 'all' ? filters.templateTypeFilter : undefined,
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
});
// Update previous values
prevFiltersRef.current = {
searchTerm: filters.searchTerm,
statusFilter: filters.statusFilter,
priorityFilter: filters.priorityFilter,
templateTypeFilter: filters.templateTypeFilter,
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
};
}, filters.searchTerm !== prev.searchTerm ? 500 : 0);
return () => clearTimeout(timeoutId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filters.searchTerm, filters.statusFilter, filters.priorityFilter, filters.templateTypeFilter, filters.sortBy, filters.sortOrder, isDealer]);
// Page change handler
const handlePageChange = useCallback(
(newPage: number) => {
if (newPage >= 1 && newPage <= closedRequests.pagination.totalPages) {
filters.setCurrentPage(newPage); // Update page in Redux
closedRequests.fetchRequests(newPage, {
search: filters.searchTerm || undefined,
status: filters.statusFilter !== 'all' ? filters.statusFilter : undefined,
priority: filters.priorityFilter !== 'all' ? filters.priorityFilter : undefined,
templateType: filters.templateTypeFilter !== 'all' ? filters.templateTypeFilter : undefined,
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
});
}
},
[closedRequests, filters]
);
// Refresh handler
const handleRefresh = useCallback(() => {
closedRequests.handleRefresh({
search: filters.searchTerm || undefined,
status: filters.statusFilter !== 'all' ? filters.statusFilter : undefined,
priority: filters.priorityFilter !== 'all' ? filters.priorityFilter : undefined,
templateType: filters.templateTypeFilter !== 'all' ? filters.templateTypeFilter : undefined,
sortBy: filters.sortBy,
sortOrder: filters.sortOrder,
});
}, [closedRequests, filters]);
return (
<div className="space-y-4 sm:space-y-6 max-w-7xl mx-auto" data-testid="closed-requests-page">
{/* Header */}
<ClosedRequestsHeader
totalRecords={closedRequests.pagination.totalRecords}
loading={closedRequests.loading}
refreshing={closedRequests.refreshing}
onRefresh={handleRefresh}
/>
{/* Filters - Plug-and-play pattern */}
<ClosedRequestsFiltersComponent
searchTerm={filters.searchTerm}
priorityFilter={filters.priorityFilter}
statusFilter={filters.statusFilter}
templateTypeFilter={filters.templateTypeFilter}
sortBy={filters.sortBy}
sortOrder={filters.sortOrder}
activeFiltersCount={
isDealer
? // For dealers: only count search and status (closure type)
[filters.searchTerm, filters.statusFilter !== 'all' ? filters.statusFilter : null].filter(Boolean).length
: // For standard users: count all filters
filters.activeFiltersCount
}
onSearchChange={filters.setSearchTerm}
onPriorityChange={filters.setPriorityFilter}
onStatusChange={filters.setStatusFilter}
onTemplateTypeChange={filters.setTemplateTypeFilter}
onSortByChange={filters.setSortBy}
onSortOrderChange={() => filters.setSortOrder(filters.sortOrder === 'asc' ? 'desc' : 'asc')}
onClearFilters={filters.clearFilters}
/>
{/* Requests List */}
<ClosedRequestsList
requests={closedRequests.requests}
loading={closedRequests.loading}
onViewRequest={onViewRequest}
/>
{/* Empty State */}
{closedRequests.requests.length === 0 && !closedRequests.loading && (
<ClosedRequestsEmpty
searchTerm={filters.searchTerm}
activeFiltersCount={filters.activeFiltersCount}
onClearFilters={filters.clearFilters}
/>
)}
{/* Pagination */}
{!closedRequests.loading && (
<ClosedRequestsPagination
pagination={closedRequests.pagination}
onPageChange={handlePageChange}
/>
)}
</div>
);
}