返回 AiToEarn
1 /**
2 * BatchActionBar - 批量模式底部固定操作栏
3 * 显示已选数量、取消按钮、删除按钮
4 */
5
6 'use client'
7
8 import { ArrowRightLeft, Loader2, Trash2 } from 'lucide-react'
9 import { memo, useCallback } from 'react'
10 import { useShallow } from 'zustand/react/shallow'
11 import { useTransClient } from '@/app/i18n/client'
12 import { Button } from '@/components/ui/button'
13 import { usePlanDetailStore } from '@/store/draft-box/planDetailStore'
14 import { useTransferDraftDialogStore } from '@/store/draft-box/transferDraftDialogStore'
15 import { cn } from '@/utils/className'
16 import { confirm } from '@/utils/ui/confirm'
17 import { toast } from '@/utils/ui/toast'
18
19 interface BatchActionBarProps {
20 allowTransfer?: boolean
21 position?: 'fixed' | 'sticky'
22 useContainerResponsive?: boolean
23 }
24
25 const BatchActionBar = memo(({ allowTransfer = true, position = 'fixed', useContainerResponsive = false }: BatchActionBarProps) => {
26 const { t } = useTransClient('brandPromotion')
27
28 const { currentPlan, selectedMaterialIds, batchDeleting, exitBatchMode, batchDeleteMaterials } = usePlanDetailStore(
29 useShallow(state => ({
30 currentPlan: state.currentPlan,
31 selectedMaterialIds: state.selectedMaterialIds,
32 batchDeleting: state.batchDeleting,
33 exitBatchMode: state.exitBatchMode,
34 batchDeleteMaterials: state.batchDeleteMaterials,
35 })),
36 )
37
38 const openTransferDialog = useTransferDraftDialogStore(state => state.openDialog)
39
40 const handleTransfer = useCallback(() => {
41 if (!currentPlan || selectedMaterialIds.length === 0) {
42 return
43 }
44
45 openTransferDialog({
46 currentPlanId: currentPlan.id,
47 draftIds: selectedMaterialIds,
48 mediaIds: [],
49 })
50 }, [currentPlan, openTransferDialog, selectedMaterialIds])
51
52 const handleDelete = useCallback(() => {
53 const count = selectedMaterialIds.length
54 if (count === 0)
55 return
56
57 confirm({
58 title: t('draftManage.batchDeleteConfirmTitle'),
59 content: t('draftManage.batchDeleteConfirmDesc', { count }),
60 okType: 'destructive',
61 onOk: async () => {
62 const success = await batchDeleteMaterials()
63 if (success) {
64 toast.success(t('draftManage.batchDeleteSuccess'))
65 }
66 else {
67 toast.error(t('draftManage.batchDeleteFailed'))
68 }
69 },
70 })
71 }, [selectedMaterialIds.length, batchDeleteMaterials, t])
72
73 return (
74 <div
75 data-testid="draftbox-batch-bar"
76 className={cn(
77 'bottom-0 z-50 border-t bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/80 px-6 py-3',
78 position === 'fixed' ? 'fixed left-0 right-0' : 'sticky -mx-4 sm:-mx-6',
79 )}
80 >
81 <div className={useContainerResponsive ? 'mx-auto flex max-w-screen-2xl flex-col gap-3 @min-[640px]:flex-row @min-[640px]:items-center @min-[640px]:justify-between' : 'flex items-center justify-between max-w-screen-2xl mx-auto'}>
82 <span data-testid="draftbox-batch-selected-count" className="text-sm text-muted-foreground">
83 {t('draftManage.selectedCount', { count: selectedMaterialIds.length })}
84 </span>
85 <div className={useContainerResponsive ? 'flex flex-wrap items-center gap-2' : 'flex items-center gap-2'}>
86 {allowTransfer && (
87 <Button
88 variant="outline"
89 size="sm"
90 onClick={handleTransfer}
91 disabled={selectedMaterialIds.length === 0 || batchDeleting}
92 className="cursor-pointer gap-1.5"
93 >
94 <ArrowRightLeft className="h-3.5 w-3.5" />
95 {t('draftManage.transfer')}
96 </Button>
97 )}
98 <Button variant="ghost" size="sm" onClick={exitBatchMode} className="cursor-pointer">
99 {t('draftManage.cancel')}
100 </Button>
101 <Button
102 data-testid="draftbox-batch-delete-btn"
103 variant="destructive"
104 size="sm"
105 onClick={handleDelete}
106 disabled={selectedMaterialIds.length === 0 || batchDeleting}
107 className="cursor-pointer gap-1.5"
108 >
109 {batchDeleting
110 ? <Loader2 className="h-3.5 w-3.5 animate-spin" />
111 : <Trash2 className="h-3.5 w-3.5" />}
112 {t('common.delete')}
113 </Button>
114 </div>
115 </div>
116 </div>
117 )
118 })
119
120 BatchActionBar.displayName = 'BatchActionBar'
121
122 export { BatchActionBar }
123
123 lines Plain Text