Initial project setup with Next.js accounts manager
- Set up Next.js project structure - Added UI components and styling - Configured package dependencies - Added feature documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		
							
								
								
									
										200
									
								
								components/accounts/batch-operations.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								components/accounts/batch-operations.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,200 @@ | ||||
| "use client"; | ||||
|  | ||||
| import { useState } from 'react'; | ||||
| import { Button } from '@/components/ui/button'; | ||||
| import { Input } from '@/components/ui/input'; | ||||
| import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'; | ||||
| import { Edit3, Trash2 } from 'lucide-react'; | ||||
| import { Account, StatsOverview } from '@/lib/types'; | ||||
| import { toast } from 'sonner'; | ||||
| import { PlatformSelector, OwnerSelector, StatusSelector } from '@/components/shared'; | ||||
|  | ||||
| interface BatchOperationsProps { | ||||
|   selectedCount: number; | ||||
|   selectedAccounts: Account[]; | ||||
|   stats: StatsOverview | null; | ||||
|   onBatchUpdate: (payload: Partial<Pick<Account, 'status' | 'ownerId' | 'notes' | 'platform'>>) => Promise<void>; | ||||
|   onBatchDelete: () => Promise<void>; | ||||
| } | ||||
|  | ||||
| export function BatchOperations({ selectedCount, selectedAccounts, stats, onBatchUpdate, onBatchDelete }: BatchOperationsProps) { | ||||
|   const [updateDialog, setUpdateDialog] = useState(false); | ||||
|   const [updateData, setUpdateData] = useState({ | ||||
|     status: '', | ||||
|     platform: '', | ||||
|     ownerId: '', | ||||
|     notes: '' | ||||
|   }); | ||||
|   const [loading, setLoading] = useState(false); | ||||
|  | ||||
|   // 计算选中账户的统计信息 | ||||
|   const getSelectedStats = () => { | ||||
|     const platforms = new Set(selectedAccounts.map(account => account.platform)); | ||||
|     const owners = new Set(selectedAccounts.map(account => account.ownerId)); | ||||
|      | ||||
|     return { | ||||
|       platformCount: platforms.size, | ||||
|       ownerCount: owners.size, | ||||
|       platforms: Array.from(platforms).slice(0, 3), // 只显示前3个 | ||||
|       owners: Array.from(owners).slice(0, 3) // 只显示前3个 | ||||
|     }; | ||||
|   }; | ||||
|  | ||||
|   const selectedStats = getSelectedStats(); | ||||
|  | ||||
|  | ||||
|   const handleBatchUpdate = async () => { | ||||
|     const payload: Partial<Pick<Account, 'status' | 'ownerId' | 'notes' | 'platform'>> = {}; | ||||
|     if (updateData.status) payload.status = updateData.status; | ||||
|     if (updateData.platform) payload.platform = updateData.platform; | ||||
|     if (updateData.ownerId) payload.ownerId = updateData.ownerId; | ||||
|     if (updateData.notes) payload.notes = updateData.notes; | ||||
|      | ||||
|     if (Object.keys(payload).length === 0) { | ||||
|       toast.warning('请至少选择一个字段进行更新'); | ||||
|       return; | ||||
|     } | ||||
|      | ||||
|     setLoading(true); | ||||
|     try { | ||||
|       await onBatchUpdate(payload); | ||||
|       setUpdateDialog(false); | ||||
|       setUpdateData({ status: '', platform: '', ownerId: '', notes: '' }); | ||||
|     } finally { | ||||
|       setLoading(false); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleBatchDelete = async () => { | ||||
|     setLoading(true); | ||||
|     try { | ||||
|       await onBatchDelete(); | ||||
|     } finally { | ||||
|       setLoading(false); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   if (selectedCount === 0) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div className="flex items-center space-x-2 p-3 bg-muted rounded-lg"> | ||||
|         <div className="flex-1"> | ||||
|           <div className="flex items-center space-x-4"> | ||||
|             <span className="text-sm font-medium">已选择 {selectedCount} 个账户</span> | ||||
|             <div className="flex items-center space-x-3 text-xs text-muted-foreground"> | ||||
|               <span> | ||||
|                 {selectedStats.ownerCount} 个用户 | ||||
|                 {selectedStats.ownerCount > 0 && ( | ||||
|                   <span className="ml-1"> | ||||
|                     ({selectedStats.owners.join(', ')} | ||||
|                     {selectedStats.ownerCount > 3 && ` 等${selectedStats.ownerCount}个`}) | ||||
|                   </span> | ||||
|                 )} | ||||
|               </span> | ||||
|               <span>•</span> | ||||
|               <span> | ||||
|                 {selectedStats.platformCount} 个平台 | ||||
|                 {selectedStats.platformCount > 0 && ( | ||||
|                   <span className="ml-1"> | ||||
|                     ({selectedStats.platforms.join(', ')} | ||||
|                     {selectedStats.platformCount > 3 && ` 等${selectedStats.platformCount}个`}) | ||||
|                   </span> | ||||
|                 )} | ||||
|               </span> | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|         <Button | ||||
|           variant="outline" | ||||
|           size="sm" | ||||
|           onClick={() => setUpdateDialog(true)} | ||||
|           disabled={loading} | ||||
|         > | ||||
|           <Edit3 className="h-4 w-4 mr-1" /> | ||||
|           批量更新 | ||||
|         </Button> | ||||
|         <Button | ||||
|           variant="destructive" | ||||
|           size="sm" | ||||
|           onClick={handleBatchDelete} | ||||
|           disabled={loading} | ||||
|         > | ||||
|           <Trash2 className="h-4 w-4 mr-1" /> | ||||
|           批量删除 | ||||
|         </Button> | ||||
|       </div> | ||||
|  | ||||
|       <Dialog open={updateDialog} onOpenChange={setUpdateDialog}> | ||||
|         <DialogContent> | ||||
|           <DialogHeader> | ||||
|             <DialogTitle>批量更新账户</DialogTitle> | ||||
|             <DialogDescription> | ||||
|               将对 {selectedCount} 个账户进行批量更新操作 | ||||
|             </DialogDescription> | ||||
|           </DialogHeader> | ||||
|           <div className="grid grid-cols-1 gap-6"> | ||||
|             <div className="space-y-3"> | ||||
|               <label className="text-sm font-medium">平台</label> | ||||
|               <PlatformSelector | ||||
|                 value={updateData.platform} | ||||
|                 onValueChange={(value) => setUpdateData({...updateData, platform: value})} | ||||
|                 stats={stats} | ||||
|                 showNoneOption={true} | ||||
|                 placeholder="选择新平台(可选)" | ||||
|               /> | ||||
|             </div> | ||||
|              | ||||
|             <div className="space-y-3"> | ||||
|               <label className="text-sm font-medium">状态</label> | ||||
|               <StatusSelector | ||||
|                 value={updateData.status} | ||||
|                 onValueChange={(value) => setUpdateData({...updateData, status: value})} | ||||
|                 stats={stats} | ||||
|                 showNoneOption={true} | ||||
|                 placeholder="选择新状态(可选)" | ||||
|               /> | ||||
|             </div> | ||||
|              | ||||
|             <div className="space-y-3"> | ||||
|               <label className="text-sm font-medium">所有者ID</label> | ||||
|               <OwnerSelector | ||||
|                 value={updateData.ownerId} | ||||
|                 onValueChange={(value) => setUpdateData({...updateData, ownerId: value})} | ||||
|                 stats={stats} | ||||
|                 showNoneOption={true} | ||||
|                 placeholder="选择所有者(可选)" | ||||
|               /> | ||||
|             </div> | ||||
|              | ||||
|             <div className="space-y-3"> | ||||
|               <label className="text-sm font-medium">备注</label> | ||||
|               <Input | ||||
|                 placeholder="输入新的备注(可选)" | ||||
|                 value={updateData.notes} | ||||
|                 onChange={(e) => setUpdateData({...updateData, notes: e.target.value})} | ||||
|               /> | ||||
|             </div> | ||||
|           </div> | ||||
|           <DialogFooter> | ||||
|             <Button  | ||||
|               variant="outline"  | ||||
|               onClick={() => setUpdateDialog(false)} | ||||
|               disabled={loading} | ||||
|             > | ||||
|               取消 | ||||
|             </Button> | ||||
|             <Button  | ||||
|               onClick={handleBatchUpdate} | ||||
|               disabled={loading} | ||||
|             > | ||||
|               {loading ? '更新中...' : '确认更新'} | ||||
|             </Button> | ||||
|           </DialogFooter> | ||||
|         </DialogContent> | ||||
|       </Dialog> | ||||
|     </> | ||||
|   ); | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Your Name
					Your Name