Files
NextEdu/src/modules/files/hooks/use-file-batch-operations.ts
SpecialX e9a5264fe7 feat(parent,auth,onboarding,files,notifications,adaptive-practice,ai): add module updates
parent:

- Add parent-student-attendance-detail component

auth:

- Add actions, data-access, schema, services, types

onboarding:

- Add parent-children-form and hooks directory

files:

- Add actions, schema, hooks directory

notifications:

- Add schema and schema test

adaptive-practice:

- Add answer-input, answer-result, practice-result-view, practice-starter-with-nav

- Add question-card, question-content, lib and services directories

ai:

- Add context/create-ai-client-service, hooks/use-drag-position, hooks/use-position-persistence
2026-07-03 10:26:12 +08:00

133 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client"
import { useCallback, useMemo, useState } from "react"
import { useRouter } from "next/navigation"
import { toast } from "sonner"
import { useTranslations } from "next-intl"
import type { FileAttachment } from "../types"
export interface UseFileBatchOperationsOptions {
files: FileAttachment[]
onAfterDelete?: () => void
}
export interface UseFileBatchOperationsReturn {
selectedIds: Set<string>
typeFilter: string
search: string
deleting: boolean
setTypeFilter: (v: string) => void
setSearch: (v: string) => void
filteredFiles: FileAttachment[]
allSelected: boolean
someSelected: boolean
toggleAll: () => void
toggleOne: (id: string) => void
handleBatchDelete: () => Promise<void>
}
/**
* 文件批量操作 hook封装筛选、选择、批量删除逻辑。
*
* 与 UI 解耦,便于在 AdminFilesView 或其他列表场景复用。
*/
export function useFileBatchOperations({
files,
onAfterDelete,
}: UseFileBatchOperationsOptions): UseFileBatchOperationsReturn {
const router = useRouter()
const t = useTranslations("files.admin")
const [typeFilter, setTypeFilter] = useState<string>("all")
const [search, setSearch] = useState<string>("")
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [deleting, setDeleting] = useState(false)
const filteredFiles = useMemo(() => {
return files.filter((f) => {
if (typeFilter !== "all") {
if (typeFilter.endsWith("/")) {
if (!f.mimeType.startsWith(typeFilter)) return false
} else if (f.mimeType !== typeFilter) {
return false
}
}
if (search.trim()) {
const kw = search.trim().toLowerCase()
if (
!f.originalName.toLowerCase().includes(kw) &&
!f.filename.toLowerCase().includes(kw)
) {
return false
}
}
return true
})
}, [files, typeFilter, search])
const allSelected = filteredFiles.length > 0 && selectedIds.size === filteredFiles.length
const someSelected = selectedIds.size > 0 && !allSelected
const toggleAll = useCallback(() => {
if (allSelected) {
setSelectedIds(new Set())
} else {
setSelectedIds(new Set(filteredFiles.map((f) => f.id)))
}
}, [allSelected, filteredFiles])
const toggleOne = useCallback((id: string) => {
setSelectedIds((prev) => {
const next = new Set(prev)
if (next.has(id)) next.delete(id)
else next.add(id)
return next
})
}, [])
const handleBatchDelete = useCallback(async () => {
if (selectedIds.size === 0) return
const ids = Array.from(selectedIds)
setDeleting(true)
try {
const res = await fetch("/api/files/batch-delete", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ids }),
})
const body = (await res.json().catch(() => null)) as {
success?: boolean
message?: string
deletedCount?: number
} | null
if (!res.ok || !body?.success) {
toast.error(body?.message || t("selection.deleteFailed"))
return
}
toast.success(t("selection.deleted", { count: body.deletedCount ?? 0 }))
setSelectedIds(new Set())
onAfterDelete?.()
router.refresh()
} catch {
toast.error(t("selection.deleteFailed"))
} finally {
setDeleting(false)
}
}, [selectedIds, onAfterDelete, router, t])
return {
selectedIds,
typeFilter,
search,
deleting,
setTypeFilter,
setSearch,
filteredFiles,
allSelected,
someSelected,
toggleAll,
toggleOne,
handleBatchDelete,
}
}