ultisuite-client/components/drive/public-share-folder-view.tsx
R3D347HR4Y d6d18f911b
Some checks failed
E2E / Playwright e2e (push) Has been cancelled
Lots of stuff and mobile app
2026-06-17 00:13:28 +02:00

279 lines
8.9 KiB
TypeScript
Raw Permalink 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 { useEffect, useMemo, useRef } from "react"
import { useRouter } from "next/navigation"
import { Upload, FolderPlus } from "lucide-react"
import { toast } from "sonner"
import { FileBrowser } from "@/components/drive/file-browser"
import { DriveFilterBar } from "@/components/drive/drive-filter-bar"
import { DriveViewModeToggle } from "@/components/drive/drive-view-mode-toggle"
import { DriveSortMenu } from "@/components/drive/drive-sort-menu"
import { DriveBulkToolbar } from "@/components/drive/drive-bulk-toolbar"
import { DriveMarqueeSurface } from "@/components/drive/drive-marquee-surface"
import { DriveScrollEndSpacer } from "@/components/drive/drive-scroll-end-spacer"
import { Button } from "@/components/ui/button"
import { usePublicShareMenuMutations, usePublicShareMutations } from "@/lib/api/hooks/use-public-share-mutations"
import type { DriveFileInfo } from "@/lib/api/types"
import { useDriveSettingsStore } from "@/lib/stores/drive-settings-store"
import { useDriveFilteredItems } from "@/lib/hooks/use-drive-filtered-items"
import { useDriveFiltersStore } from "@/lib/stores/drive-filters-store"
import {
sharePermCanCreate,
sharePermCanDelete,
sharePermCanEdit,
sharePermCanUpdate,
} from "@/lib/drive/drive-share-permissions"
import { openPublicShareItem, downloadPublicShareFile } from "@/lib/drive/open-public-share-item"
import { useDriveUIStore } from "@/lib/stores/drive-ui-store"
import {
DRIVE_BROWSER_CARD_CLASS,
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
DRIVE_FILTER_CONTENT_GAP,
DRIVE_FILTER_LIST_CONTENT_GAP,
} from "@/lib/drive/drive-chrome-classes"
import { cn } from "@/lib/utils"
import { nextUntitledName } from "@/lib/drive/drive-default-name"
export function PublicShareFolderView({
token,
folderPath,
files,
permissions,
password,
folderTitle,
}: {
token: string
folderPath: string
files: DriveFileInfo[]
permissions: number
password?: string
folderTitle: string
}) {
const router = useRouter()
const openPreview = useDriveUIStore((s) => s.openPreview)
const selectedPaths = useDriveUIStore((s) => s.selectedPaths)
const clearSelection = useDriveUIStore((s) => s.clearSelection)
const sortField = useDriveSettingsStore((s) => s.sortField)
const sortDir = useDriveSettingsStore((s) => s.sortDir)
const folderPlacement = useDriveSettingsStore((s) => s.folderPlacement)
const viewMode = useDriveSettingsStore((s) => s.viewMode)
const filterContentGap =
viewMode === "list" ? DRIVE_FILTER_LIST_CONTENT_GAP : DRIVE_FILTER_CONTENT_GAP
const filters = useDriveFiltersStore()
const uploadInputRef = useRef<HTMLInputElement>(null)
const canEdit = sharePermCanEdit(permissions)
const canCreate = sharePermCanCreate(permissions)
const canUpdate = sharePermCanUpdate(permissions)
const canDelete = sharePermCanDelete(permissions)
const writable = canUpdate || canDelete
const mutations = usePublicShareMenuMutations(token, password)
const { uploadFile, createFolder } = usePublicShareMutations(token, password)
const filtersSnapshot = useMemo(
() => ({
types: filters.types,
sources: filters.sources,
contactEmail: filters.contactEmail,
contactName: filters.contactName,
datePreset: filters.datePreset,
dateFrom: filters.dateFrom,
dateTo: filters.dateTo,
}),
[
filters.types,
filters.sources,
filters.contactEmail,
filters.contactName,
filters.datePreset,
filters.dateFrom,
filters.dateTo,
]
)
const { filteredItems: filteredFiles } = useDriveFilteredItems(
files,
filtersSnapshot,
{ sortField, sortDir, folderPlacement },
{ scopePath: folderPath }
)
const selectedTargets = useMemo(
() => filteredFiles.filter((f) => selectedPaths.has(f.path)),
[filteredFiles, selectedPaths]
)
const showBulk = selectedTargets.length > 0
useEffect(() => {
clearSelection()
}, [folderPath, clearSelection])
const openItem = (file: DriveFileInfo) => {
openPublicShareItem(file, {
token,
password,
canEdit: sharePermCanEdit(permissions),
router,
openPreview,
contextItems: filteredFiles,
})
}
const handleUpload = async (fileList: FileList | null) => {
if (!fileList?.length || !canCreate) return
const base = folderPath === "/" ? "" : folderPath
try {
for (const file of Array.from(fileList)) {
await uploadFile(`${base}/${file.name}`.replace(/\/+/g, "/"), file)
}
toast.success("Fichier(s) importé(s)")
} catch {
toast.error("Impossible dimporter le fichier")
}
}
const handleNewFolder = async () => {
if (!canCreate) return
const name = nextUntitledName(
filteredFiles.filter((f) => f.type === "directory").map((f) => f.name),
"Dossier"
)
const base = folderPath === "/" ? "" : folderPath
const path = `${base}/${name}`.replace(/\/+/g, "/")
try {
await createFolder.mutateAsync(path)
toast.success("Dossier créé")
} catch {
toast.error("Impossible de créer le dossier")
}
}
const downloadBulk = async (targets: DriveFileInfo[]) => {
for (const file of targets) {
await downloadPublicShareFile(token, file, password)
}
}
return (
<div className={DRIVE_BROWSER_CARD_CLASS}>
<div className={cn("shrink-0", filterContentGap)}>
<div
className={cn(
"flex min-h-12 shrink-0 flex-wrap items-center justify-between gap-3 py-2",
DRIVE_CARD_PAD_X
)}
>
<p className="truncate text-sm text-muted-foreground">
<span className="font-medium text-[#3c4043] dark:text-[#e8eaed]">{folderTitle}</span>
{" · "}
{filteredFiles.length} élément{filteredFiles.length > 1 ? "s" : ""}
{canEdit ? " · Éditeur" : " · Lecture seule"}
</p>
<div className="flex flex-wrap items-center gap-2">
{canCreate ? (
<>
<input
ref={uploadInputRef}
type="file"
multiple
className="hidden"
onChange={(e) => void handleUpload(e.target.files)}
/>
<Button
type="button"
variant="outline"
size="sm"
className="gap-2"
onClick={() => uploadInputRef.current?.click()}
>
<Upload className="h-4 w-4" />
Importer
</Button>
<Button
type="button"
variant="outline"
size="sm"
className="gap-2"
onClick={() => void handleNewFolder()}
>
<FolderPlus className="h-4 w-4" />
Dossier
</Button>
</>
) : null}
<DriveSortMenu />
<DriveViewModeToggle />
</div>
</div>
{showBulk ? (
<DriveBulkToolbar
targets={selectedTargets}
allowShare={false}
allowMove={false}
allowCopy={false}
allowQuickLink={false}
allowDelete={canDelete}
mutations={mutations}
onDownloadBulk={downloadBulk}
/>
) : (
<DriveFilterBar showContacts={false} />
)}
</div>
<main
data-drive-browser-main
className="flex min-h-0 flex-1 flex-col overflow-auto"
>
<DriveMarqueeSurface
enabled={filteredFiles.length > 0}
className="min-h-full"
>
{files.length === 0 ? (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-sm text-muted-foreground"
)}
>
Ce dossier est vide.
{canCreate ? " Importez un fichier ou créez un dossier." : ""}
</p>
) : filteredFiles.length === 0 ? (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-sm text-muted-foreground"
)}
>
Aucun élément ne correspond aux filtres.
</p>
) : (
<FileBrowser
items={filteredFiles}
view="shared"
allowShare={false}
writable={writable}
hideFavorite
disableDnd
mutations={mutations}
publicShare={{ token, password }}
onOpenItem={openItem}
onDownloadItem={(file) =>
void downloadPublicShareFile(token, file, password).catch(() =>
toast.error("Téléchargement impossible")
)
}
/>
)}
<DriveScrollEndSpacer />
</DriveMarqueeSurface>
</main>
</div>
)
}