ultisuite-client/lib/hooks/use-drive-new-menu.ts
R3D347HR4Y 303b2b1074
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
wow
2026-06-11 01:22:40 +02:00

179 lines
5.5 KiB
TypeScript

"use client"
import { useMemo, useState } from "react"
import { useRouter } from "next/navigation"
import { useQueryClient } from "@tanstack/react-query"
import { toast } from "sonner"
import { useDriveList, useDriveMutations } from "@/lib/api/hooks/use-drive-queries"
import { uploadFile } from "@/lib/api/upload"
import { nextUntitledName } from "@/lib/drive/drive-default-name"
import { apiClient, ApiRequestError } from "@/lib/api/client"
import type { DriveFileInfo } from "@/lib/api/types"
import { buildDriveDrawEditHref, buildDriveEditHref } from "@/lib/drive/drive-url"
export type DriveNewKind = "document" | "spreadsheet" | "presentation" | "drawing" | "folder"
export const DRIVE_NEW_KIND_META: Record<
DriveNewKind,
{ ext: string; typeLabel: string; menuLabel: string }
> = {
document: { ext: ".docx", typeLabel: "Document", menuLabel: "Document" },
spreadsheet: { ext: ".xlsx", typeLabel: "Tableur", menuLabel: "Tableur" },
presentation: { ext: ".pptx", typeLabel: "Présentation", menuLabel: "Présentation" },
drawing: { ext: ".excalidraw", typeLabel: "Dessin", menuLabel: "Dessin" },
folder: { ext: "", typeLabel: "Dossier", menuLabel: "Dossier" },
}
export const DRIVE_NEW_MENU_ITEM_CLASS =
"gap-3 rounded-md py-2.5 pr-3 pl-3 text-[15px] focus:bg-accent/80 [&_svg]:size-5"
async function importFolderTree(
files: FileList,
parentPath: string,
createFolder: ReturnType<typeof useDriveMutations>["createFolder"]
) {
const base = parentPath === "/" ? "" : parentPath
const created = new Set<string>()
const ensureDir = async (dirPath: string) => {
if (created.has(dirPath)) return
try {
await createFolder.mutateAsync(dirPath)
} catch {
/* dossier peut déjà exister */
}
created.add(dirPath)
}
for (const file of Array.from(files)) {
const rel = file.webkitRelativePath
if (!rel) continue
const segments = rel.split("/")
const name = segments.pop()
if (!name) continue
let dirAcc = base
for (const segment of segments) {
dirAcc = `${dirAcc}/${segment}`
await ensureDir(dirAcc)
}
await uploadFile(`${dirAcc}/${name}`, file)
}
}
export function useDriveNewMenu(parentPath: string) {
const router = useRouter()
const queryClient = useQueryClient()
const { createFolder, createFile } = useDriveMutations()
const base = parentPath === "/" ? "" : parentPath
const list = useDriveList(parentPath)
const siblingNames = useMemo(
() => (list.data?.files ?? []).map((f) => f.name),
[list.data?.files]
)
const [pendingKind, setPendingKind] = useState<DriveNewKind | null>(null)
const refresh = () => queryClient.invalidateQueries({ queryKey: ["drive"] })
const pendingMeta = pendingKind ? DRIVE_NEW_KIND_META[pendingKind] : null
const defaultName =
pendingMeta && pendingKind
? nextUntitledName(siblingNames, pendingMeta.typeLabel, pendingMeta.ext)
: ""
const confirmNew = async (rawName: string) => {
if (!pendingKind || !pendingMeta) return
const name = rawName.trim().replace(/\//g, "")
if (!name) return
if (pendingKind === "folder") {
try {
await createFolder.mutateAsync(`${base}/${name}`)
toast.success("Dossier créé")
await refresh()
} catch {
toast.error("Impossible de créer le dossier")
throw new Error("folder create failed")
}
return
}
const fileName = name.endsWith(pendingMeta.ext) ? name : name + pendingMeta.ext
try {
const result = await createFile.mutateAsync({
parent_path: parentPath,
name: fileName,
kind: pendingKind,
})
toast.success("Fichier créé")
if (pendingKind === "drawing") {
let fileId = result.file_id
if (!fileId) {
const info = await apiClient.get<DriveFileInfo>(
`/drive/files/info${result.path.startsWith("/") ? result.path : `/${result.path}`}`
)
fileId = info.file_id
}
if (fileId) {
router.push(buildDriveDrawEditHref(fileId))
} else {
throw new Error("file id missing")
}
return
}
const returnTo =
typeof window !== "undefined"
? window.location.pathname + window.location.search
: undefined
router.push(buildDriveEditHref(result.path, returnTo))
} catch (error) {
const detail =
error instanceof ApiRequestError
? error.message
: error instanceof Error
? error.message
: null
toast.error(detail ? `Impossible de créer le fichier : ${detail}` : "Impossible de créer le fichier")
throw new Error("file create failed")
}
}
const uploadFiles = async (files: FileList | null) => {
if (!files?.length) return
for (const file of Array.from(files)) {
try {
await uploadFile(`${base}/${file.name}`, file)
} catch {
toast.error(`Échec : ${file.name}`)
}
}
await refresh()
toast.success("Import terminé")
}
const importFolder = async (files: FileList | null) => {
if (!files?.length) return
try {
await importFolderTree(files, parentPath, createFolder)
await refresh()
toast.success("Dossier importé")
} catch {
toast.error("Impossible d'importer le dossier")
}
}
const pickKind = (kind: DriveNewKind) => setPendingKind(kind)
const closeNameDialog = () => setPendingKind(null)
return {
pendingKind,
pendingMeta,
defaultName,
confirmNew,
uploadFiles,
importFolder,
pickKind,
closeNameDialog,
}
}