ultisuite-client/app/drive/(browser)/[[...segments]]/page.tsx
R3D347HR4Y ad1370ea7e
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat: enhance configuration and add new demo layouts
- Introduced turbopack alias for canvas in next.config.mjs.
- Updated package.json scripts for development and branding tasks.
- Added new dependencies for Tiptap extensions.
- Implemented new demo layouts for agenda, contacts, drive, and mail applications.
- Enhanced globals.css for improved theming and splash screen animations.
- Added OAuth callback handling for drive mounts.
- Updated layout components to integrate new demo shells and improve structure.
2026-06-12 19:10:24 +02:00

295 lines
9.3 KiB
TypeScript

"use client"
import { useEffect, useMemo, useState } from "react"
import { useParams, useSearchParams } from "next/navigation"
import { DriveHeader } from "@/components/drive/drive-header"
import { DriveMobileBottomBar } from "@/components/drive/drive-mobile-bottom-bar"
import { DriveBrowserChrome } from "@/components/drive/drive-browser-chrome"
import { FileBrowser } from "@/components/drive/file-browser"
import { DriveMarqueeSurface } from "@/components/drive/drive-marquee-surface"
import { DriveScrollEndSpacer } from "@/components/drive/drive-scroll-end-spacer"
import { parseDriveSegments, folderPathFromSegments } from "@/lib/drive/drive-url"
import {
type DriveSearchScope,
defaultDriveSearchScope,
fileBrowserViewForSearchScope,
parseDriveSearchParams,
} from "@/lib/drive/drive-search"
import { useDriveFilteredItems } from "@/lib/hooks/use-drive-filtered-items"
import { useDriveFiltersStore } from "@/lib/stores/drive-filters-store"
import { useDriveSettingsStore } from "@/lib/stores/drive-settings-store"
import {
DRIVE_BROWSER_CARD_CLASS,
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
DRIVE_MAIN_INSET_X,
} from "@/lib/drive/drive-chrome-classes"
import { cn } from "@/lib/utils"
import {
useDriveList,
useDriveMountList,
useDriveOrgList,
useDriveRecent,
useDriveSearch,
useDriveSharedWithMe,
useDriveStarred,
useDriveTrash,
} from "@/lib/api/hooks/use-drive-queries"
import { pathRefFromRoute } from "@/lib/api/drive-roots"
export default function DriveBrowserPage() {
const params = useParams()
const urlSearchParams = useSearchParams()
const segments = params.segments as string[] | undefined
const route = useMemo(() => parseDriveSegments(segments), [segments])
const folderPath = folderPathFromSegments(route.pathSegments)
const contextView =
route.view === "shared"
? "shared"
: route.view === "search"
? "files"
: route.view === "org" || route.view === "mount"
? route.view
: route.view
const fallbackScope = defaultDriveSearchScope(
route.view === "shared" ? "shared" : "files",
folderPath
)
const committedSearch = useMemo(() => {
if (route.view !== "search") return null
return parseDriveSearchParams(urlSearchParams, {
scope: fallbackScope,
folderPath,
})
}, [route.view, urlSearchParams, fallbackScope, folderPath])
const [searchInput, setSearchInput] = useState("")
const [searchScope, setSearchScope] = useState<DriveSearchScope>(fallbackScope)
useEffect(() => {
if (route.view === "search" && committedSearch) {
setSearchInput(committedSearch.query)
setSearchScope(committedSearch.scope)
}
}, [route.view, committedSearch?.query, committedSearch?.scope])
useEffect(() => {
if (route.view !== "search") {
setSearchScope(fallbackScope)
}
}, [route.view, fallbackScope])
const filters = useDriveFiltersStore()
const sortField = useDriveSettingsStore((s) => s.sortField)
const sortDir = useDriveSettingsStore((s) => s.sortDir)
const folderPlacement = useDriveSettingsStore((s) => s.folderPlacement)
const list = useDriveList(folderPath, route.page, "", route.view === "files")
const orgList = useDriveOrgList(route.rootId ?? "", folderPath, route.page, route.view === "org" && Boolean(route.rootId))
const mountList = useDriveMountList(route.rootId ?? "", folderPath, route.page, route.view === "mount" && Boolean(route.rootId))
const shared = useDriveSharedWithMe(
route.page,
"",
route.view === "shared" && route.pathSegments.length === 0
)
const sharedFolder = useDriveList(
folderPath,
route.page,
"",
route.view === "shared" && route.pathSegments.length > 0
)
const recent = useDriveRecent()
const starred = useDriveStarred(folderPath)
const trash = useDriveTrash()
const searchResults = useDriveSearch(
committedSearch?.query ?? "",
committedSearch?.scope ?? "all",
committedSearch?.scope === "folder" ? committedSearch.folderPath : "/",
route.page,
route.view === "search" && Boolean(committedSearch?.query)
)
const active =
route.view === "search"
? searchResults
: route.view === "recent"
? recent
: route.view === "starred"
? starred
: route.view === "trash"
? trash
: route.view === "shared"
? route.pathSegments.length === 0
? shared
: sharedFolder
: route.view === "org"
? orgList
: route.view === "mount"
? mountList
: list
const files = active.data?.files ?? []
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 browseWithSubtree =
route.view === "files" ||
(route.view === "shared" && route.pathSegments.length > 0)
const { filteredItems: filteredFiles, corpusLoading } = useDriveFilteredItems(
files,
filtersSnapshot,
{ sortField, sortDir, folderPlacement },
{
recursiveCorpus: browseWithSubtree,
scopePath: folderPath,
}
)
const isLoading = active.isLoading || corpusLoading
const isTrash = route.view === "trash"
const isSearchView = route.view === "search"
const searchBrowserView = committedSearch
? fileBrowserViewForSearchScope(committedSearch.scope)
: "files"
const emptyMessage = isSearchView
? committedSearch?.query
? "Aucun résultat pour cette recherche."
: "Saisissez un terme de recherche."
: "Ce dossier est vide."
return (
<>
<DriveHeader
search={searchInput}
onSearchChange={setSearchInput}
searchScope={searchScope}
onSearchScopeChange={setSearchScope}
folderPath={folderPath}
contextView={contextView}
/>
<div
className={cn(
"flex min-h-0 flex-1 flex-col pb-1 max-sm:pb-0",
DRIVE_MAIN_INSET_X
)}
>
<div className={DRIVE_BROWSER_CARD_CLASS} data-drive-browser-card>
<DriveBrowserChrome
view={route.view}
segments={route.pathSegments}
rootId={route.rootId}
isTrash={isTrash}
items={filteredFiles}
searchState={committedSearch}
/>
<main
data-drive-browser-main
className="flex min-h-0 flex-1 flex-col overflow-auto"
>
<DriveMarqueeSurface
enabled={!isLoading && !active.isError && filteredFiles.length > 0}
className="min-h-full"
>
{isLoading && (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-muted-foreground"
)}
>
Chargement
</p>
)}
{active.isError && (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-destructive"
)}
>
{isSearchView
? "Impossible de charger les résultats de recherche."
: "Impossible de charger les fichiers."}
</p>
)}
{!isLoading && !active.isError && files.length === 0 && (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-muted-foreground"
)}
>
{emptyMessage}
</p>
)}
{!isLoading && !active.isError && files.length > 0 && filteredFiles.length === 0 && (
<p
className={cn(
DRIVE_CARD_PAD_X,
DRIVE_CARD_SCROLL_PT,
"py-8 text-center text-muted-foreground"
)}
>
Aucun élément ne correspond aux filtres.
</p>
)}
{filteredFiles.length > 0 ? (
<FileBrowser
items={filteredFiles}
view={
isSearchView
? searchBrowserView
: route.view === "shared"
? "shared"
: route.view
}
rootId={route.rootId}
isTrash={isTrash}
/>
) : null}
<DriveScrollEndSpacer />
</DriveMarqueeSurface>
</main>
</div>
</div>
<DriveMobileBottomBar
search={searchInput}
onSearchChange={setSearchInput}
searchScope={searchScope}
onSearchScopeChange={setSearchScope}
folderPath={folderPath}
contextView={contextView}
resultsMode={isSearchView}
parentPath={folderPath}
/>
</>
)
}