207 lines
7.9 KiB
TypeScript
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>
|
|
);
|
|
}
|
|
|