| 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 |