diff --git a/src/main/services/database/extractor-operation-history-dao.ts b/src/main/services/database/extractor-operation-history-dao.ts index 5e24db2..a19df05 100644 --- a/src/main/services/database/extractor-operation-history-dao.ts +++ b/src/main/services/database/extractor-operation-history-dao.ts @@ -173,10 +173,7 @@ export class ExtractorOperationHistoryDAO { * @param status - New status (success, failed, partial) * @returns Update result */ - async updateBatchStatus( - batchId: string, - status: string - ): Promise { + async updateBatchStatus(batchId: string, status: string): Promise { try { const dbService = await this.getDatabaseService() const tableName = this.getTableName() @@ -265,7 +262,7 @@ export class ExtractorOperationHistoryDAO { /** * Get batch statistics with optional user filtering * @param userId - Optional user ID for filtering (Admin gets all, User gets own) - * @param options - Query options (limit, offset) + * @param options - Query options (limit, offset, usernames) * @returns Array of batch statistics */ async getBatches(userId?: number, options?: GetBatchesOptions): Promise { @@ -293,6 +290,11 @@ export class ExtractorOperationHistoryDAO { if (userId !== undefined) { sqlString += isSqlServer ? ` WHERE UserId = @p0 ` : ` WHERE UserId = ? ` params.push(userId) + } else if (options?.usernames && options.usernames.length > 0) { + // Admin user filtering by multiple usernames using IN clause + const placeholders = this.buildPlaceholders(options.usernames.length, isSqlServer) + sqlString += ` WHERE Username IN (${placeholders}) ` + params.push(...options.usernames) } sqlString += ` @@ -573,9 +575,10 @@ export class ExtractorOperationHistoryDAO { /** * Count total batches with optional user filtering * @param userId - Optional user ID for filtering + * @param usernames - Optional usernames filter for Admin users * @returns Total number of batches */ - async countBatches(userId?: number): Promise { + async countBatches(userId?: number, usernames?: string[]): Promise { try { const dbService = await this.getDatabaseService() const tableName = this.getTableName() @@ -586,11 +589,16 @@ export class ExtractorOperationHistoryDAO { FROM ${tableName} ` - const params: number[] = [] + const params: (number | string)[] = [] if (userId !== undefined) { sqlString += isSqlServer ? ` WHERE UserId = @p0 ` : ` WHERE UserId = ? ` params.push(userId) + } else if (usernames && usernames.length > 0) { + // Admin user filtering by multiple usernames using IN clause + const placeholders = this.buildPlaceholders(usernames.length, isSqlServer) + sqlString += ` WHERE Username IN (${placeholders}) ` + params.push(...usernames) } const result = await dbService.query(sqlString, params) diff --git a/src/main/types/operation-history.types.ts b/src/main/types/operation-history.types.ts index a9e1852..458677e 100644 --- a/src/main/types/operation-history.types.ts +++ b/src/main/types/operation-history.types.ts @@ -83,4 +83,6 @@ export interface GetBatchesOptions { limit?: number /** Number of batches to skip (for pagination) */ offset?: number + /** Optional username filter for Admin users (supports multiple) */ + usernames?: string[] } diff --git a/src/renderer/src/components/ExtractorOperationHistoryModal.tsx b/src/renderer/src/components/ExtractorOperationHistoryModal.tsx index f148ffb..a4428c6 100644 --- a/src/renderer/src/components/ExtractorOperationHistoryModal.tsx +++ b/src/renderer/src/components/ExtractorOperationHistoryModal.tsx @@ -80,6 +80,8 @@ export const ExtractorOperationHistoryModal: React.FC>(new Set()) const [batchDetails, setBatchDetails] = useState>(new Map()) const [deleting, setDeleting] = useState>(new Set()) + const [allUsers, setAllUsers] = useState([]) + const [selectedUsers, setSelectedUsers] = useState([]) const isAdmin = user?.userType === 'Admin' @@ -87,7 +89,13 @@ export const ExtractorOperationHistoryModal: React.FC 0 + ? { limit: 100, usernames: selectedUsers } + : { limit: 100 } + + const result = await window.electron.operationHistory.getBatches(options) if (result.success && result.data) { setBatches(result.data) } else { @@ -98,6 +106,18 @@ export const ExtractorOperationHistoryModal: React.FC { + try { + const result = await window.electron.auth.getAllUsers() + if (result.success && result.data) { + const usernames = result.data.map((u: UserInfo) => u.username) + setAllUsers(usernames) + } + } catch (err) { + console.error('Failed to fetch users:', err) + } }, []) const fetchBatchDetails = useCallback( @@ -123,8 +143,11 @@ export const ExtractorOperationHistoryModal: React.FC { if (isOpen) { void fetchBatches() + if (isAdmin) { + void fetchAllUsers() + } } - }, [isOpen, fetchBatches]) + }, [isOpen, fetchBatches, fetchAllUsers, isAdmin]) const toggleBatchExpansion = (batchId: string) => { setExpandedBatches((prev) => { @@ -196,27 +219,74 @@ export const ExtractorOperationHistoryModal: React.FC { + setSelectedUsers((prev) => + prev.includes(username) + ? prev.filter((u) => u !== username) + : [...prev, username] + ) + } + + const clearUserFilters = () => { + setSelectedUsers([]) + } + if (!isOpen) return null return (
{/* Toolbar */} -
-
- - {isAdmin ? ( - 管理员模式:显示所有用户记录 - ) : ( - 仅显示您的操作记录 - )} - - {batches.length > 0 && ( - 共 {batches.length} 条批次 +
+
+ {isAdmin && allUsers.length > 0 && ( +
+
筛选用户:
+
+ {allUsers.map((username) => { + const isSelected = selectedUsers.includes(username) + return ( + + ) + })} + {selectedUsers.length > 0 && ( + + )} +
+
)} +
+ + {isAdmin ? ( + + 管理员模式:{selectedUsers.length > 0 ? `已选择 ${selectedUsers.length} 个用户` : '显示所有用户记录'} + + ) : ( + 仅显示您的操作记录 + )} + + {batches.length > 0 && ( + 共 {batches.length} 条批次 + )} +
- + {isAdmin && ( + + )}
{/* Batch details */} diff --git a/src/renderer/src/components/app/AuthenticatedAppShell.tsx b/src/renderer/src/components/app/AuthenticatedAppShell.tsx index d0dce91..2a9de29 100644 --- a/src/renderer/src/components/app/AuthenticatedAppShell.tsx +++ b/src/renderer/src/components/app/AuthenticatedAppShell.tsx @@ -185,7 +185,7 @@ export function AuthenticatedAppShell({
)} - {currentPage === 'extractor' && } + {currentPage === 'extractor' && } {currentPage === 'cleaner' && } {currentPage === 'settings' && } diff --git a/src/renderer/src/pages/ExtractorPage.tsx b/src/renderer/src/pages/ExtractorPage.tsx index 195d813..33db177 100644 --- a/src/renderer/src/pages/ExtractorPage.tsx +++ b/src/renderer/src/pages/ExtractorPage.tsx @@ -7,12 +7,28 @@ import { useSharedProductionIds } from '../hooks/useSharedProductionIds' import LogPanel from '../components/ui/LogPanel' import { SegmentedProgressBar } from '../components/ui/SegmentedProgressBar' import ExtractorOperationHistoryModal from '../components/ExtractorOperationHistoryModal' -import { useUserStore } from '../stores/useUserStore' +import type { CurrentUser } from '../hooks/useAppBootstrap' -const ExtractorPage: React.FC = () => { +interface ExtractorPageProps { + currentUser: CurrentUser | null +} + +const ExtractorPage: React.FC = ({ currentUser }) => { const [orderNumbers, setOrderNumbers] = usePersistentTextState('extractor_orderNumbers') const [showHistoryModal, setShowHistoryModal] = React.useState(false) - const user = useUserStore((state) => state.user) + + // Convert currentUser to UserInfo format for the modal + const user = React.useMemo( + () => + currentUser + ? { + id: 0, // ID is not needed for modal display logic + username: currentUser.username, + userType: currentUser.userType + } + : null, + [currentUser] + ) const { isRunning,