Add audit trail and filters

Implements audit trail view for item approvals, adds approval date range filtering to moderation queue, and wires up UI and backend components (Approval History page, ItemApprovalHistory component, materialized view-based history, and query/filters integration) to support compliant reporting and time-based moderation filtering.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-12 14:06:34 +00:00
parent 7b0825e772
commit b22546e7f2
13 changed files with 872 additions and 10 deletions

View File

@@ -12,7 +12,7 @@ import { useState, useCallback, useEffect } from 'react';
import { useDebounce } from '@/hooks/useDebounce';
import { logger } from '@/lib/logger';
import { MODERATION_CONSTANTS } from '@/lib/moderation/constants';
import type { EntityFilter, StatusFilter, QueueTab, SortConfig, SortField } from '@/types/moderation';
import type { EntityFilter, StatusFilter, QueueTab, SortConfig, SortField, ApprovalDateRangeFilter } from '@/types/moderation';
import * as storage from '@/lib/localStorage';
export interface ModerationFiltersConfig {
@@ -36,6 +36,9 @@ export interface ModerationFiltersConfig {
/** Initial sort configuration */
initialSortConfig?: SortConfig;
/** Initial approval date range filter */
initialApprovalDateRange?: ApprovalDateRangeFilter;
}
export interface ModerationFilters {
@@ -87,6 +90,15 @@ export interface ModerationFilters {
/** Reset sort to default */
resetSort: () => void;
/** Approval date range filter (immediate) */
approvalDateRange: ApprovalDateRangeFilter;
/** Debounced approval date range (use this for queries) */
debouncedApprovalDateRange: ApprovalDateRangeFilter;
/** Set approval date range */
setApprovalDateRange: (range: ApprovalDateRangeFilter) => void;
/** Reset pagination to page 1 (callback) */
onFilterChange?: () => void;
}
@@ -121,6 +133,7 @@ export function useModerationFilters(
persist = true,
storageKey = 'moderationQueue_filters',
initialSortConfig = { field: 'created_at', direction: 'asc' },
initialApprovalDateRange = { from: null, to: null },
onFilterChange,
} = config;
@@ -174,6 +187,9 @@ export function useModerationFilters(
// Sort state
const [sortConfig, setSortConfigState] = useState<SortConfig>(loadPersistedSort);
// Approval date range state
const [approvalDateRange, setApprovalDateRangeState] = useState<ApprovalDateRangeFilter>(initialApprovalDateRange);
// Debounced filters for API calls
const debouncedEntityFilter = useDebounce(entityFilter, debounceDelay);
@@ -181,6 +197,9 @@ export function useModerationFilters(
// Debounced sort (0ms for immediate feedback)
const debouncedSortConfig = useDebounce(sortConfig, 0);
// Debounced approval date range
const debouncedApprovalDateRange = useDebounce(approvalDateRange, debounceDelay);
// Persist filters to localStorage
useEffect(() => {
@@ -246,6 +265,13 @@ export function useModerationFilters(
const resetSort = useCallback(() => {
setSortConfigState(initialSortConfig);
}, [initialSortConfig]);
// Set approval date range with logging and pagination reset
const setApprovalDateRange = useCallback((range: ApprovalDateRangeFilter) => {
logger.log('🔍 Approval date range changed:', range);
setApprovalDateRangeState(range);
onFilterChange?.();
}, [onFilterChange]);
// Clear all filters
const clearFilters = useCallback(() => {
@@ -254,7 +280,8 @@ export function useModerationFilters(
setStatusFilterState(initialStatusFilter);
setActiveTabState(initialTab);
setSortConfigState(initialSortConfig);
}, [initialEntityFilter, initialStatusFilter, initialTab, initialSortConfig]);
setApprovalDateRangeState(initialApprovalDateRange);
}, [initialEntityFilter, initialStatusFilter, initialTab, initialSortConfig, initialApprovalDateRange]);
// Check if non-default filters are active
const hasActiveFilters =
@@ -262,7 +289,9 @@ export function useModerationFilters(
statusFilter !== initialStatusFilter ||
activeTab !== initialTab ||
sortConfig.field !== initialSortConfig.field ||
sortConfig.direction !== initialSortConfig.direction;
sortConfig.direction !== initialSortConfig.direction ||
approvalDateRange.from !== null ||
approvalDateRange.to !== null;
// Return without useMemo wrapper (OPTIMIZED)
return {
@@ -282,6 +311,9 @@ export function useModerationFilters(
sortBy,
toggleSortDirection,
resetSort,
approvalDateRange,
debouncedApprovalDateRange,
setApprovalDateRange,
onFilterChange,
};
}

View File

@@ -174,6 +174,7 @@ export function useModerationQueueManager(config: ModerationQueueManagerConfig):
currentPage: pagination.currentPage,
pageSize: pagination.pageSize,
sortConfig: filters.debouncedSortConfig,
approvalDateRange: filters.debouncedApprovalDateRange,
enabled: !!user,
});

View File

@@ -98,6 +98,12 @@ export interface UseQueueQueryConfig {
direction: SortDirection;
};
/** Approval date range filter */
approvalDateRange?: {
from: Date | null;
to: Date | null;
};
/** Whether query is enabled (defaults to true) */
enabled?: boolean;
}
@@ -145,6 +151,7 @@ export function useQueueQuery(config: UseQueueQueryConfig): UseQueueQueryReturn
currentPage: config.currentPage,
pageSize: config.pageSize,
sortConfig: config.sortConfig,
approvalDateRange: config.approvalDateRange,
};
// Create stable query key (TanStack Query uses this for caching/deduplication)
@@ -161,6 +168,8 @@ export function useQueueQuery(config: UseQueueQueryConfig): UseQueueQueryReturn
config.pageSize,
config.sortConfig.field,
config.sortConfig.direction,
config.approvalDateRange?.from?.toISOString(),
config.approvalDateRange?.to?.toISOString(),
];
// Execute query