ultisuite-client/components/mail/mail-drive-folder-picker.tsx
R3D347HR4Y 6ec95262af Add OnlyOffice integration and update project configurations
- Updated .env.example to include configuration for OnlyOffice Document Server.
- Modified the workspace configuration to remove the drive-suite path.
- Adjusted TypeScript environment imports for consistency.
- Enhanced Next.js configuration to disable canvas in Webpack.
- Updated package.json to include new dependencies for OnlyOffice and PDF.js.
- Added global styles for OnlyOffice theme integration in the CSS.
- Created new layout and page components for the Drive feature, including public sharing and editing functionalities.
- Updated metadata handling across various layouts to reflect the new app structure.
2026-06-07 15:49:21 +02:00

167 lines
5.5 KiB
TypeScript

"use client"
import { useMemo, useState } from "react"
import { ChevronRight, Folder } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { useDriveList } from "@/lib/api/hooks/use-drive-queries"
import { displayFileName } from "@/lib/drive/display-file-name"
import {
DRIVE_BTN_GHOST,
DRIVE_BTN_PRIMARY,
DRIVE_DIALOG_CONTENT,
DRIVE_DIALOG_DIVIDER,
DRIVE_DIALOG_FOOTER,
DRIVE_DIALOG_OVERLAY,
DRIVE_TEXT_PRIMARY,
DRIVE_TEXT_SECONDARY,
DRIVE_TEXT_TITLE,
} from "@/lib/drive/drive-dialog-styles"
import { normalizeDriveFolderPath } from "@/lib/drive/drive-sidebar-tree"
import { MAIL_DRIVE_DEFAULT_FOLDER } from "@/lib/mail/mail-drive"
import { cn } from "@/lib/utils"
export function MailDriveFolderPicker({
open,
onOpenChange,
title,
description,
confirmLabel,
pending,
onConfirm,
}: {
open: boolean
onOpenChange: (open: boolean) => void
title: string
description?: string
confirmLabel: string
pending?: boolean
onConfirm: (folderPath: string) => void | Promise<void>
}) {
const [browsePath, setBrowsePath] = useState(MAIL_DRIVE_DEFAULT_FOLDER)
const list = useDriveList(browsePath, 1, "", open)
const folders = useMemo(
() => (list.data?.files ?? []).filter((f) => f.type === "directory"),
[list.data?.files]
)
const crumbs = useMemo(() => {
const normalized = normalizeDriveFolderPath(browsePath)
if (normalized === "/") return [{ path: "/", label: "Mon Drive" }]
const parts = normalized.slice(1).split("/")
const out: { path: string; label: string }[] = [{ path: "/", label: "Mon Drive" }]
for (let i = 0; i < parts.length; i++) {
const path = "/" + parts.slice(0, i + 1).join("/")
out.push({ path, label: displayFileName(parts[i]!) })
}
return out
}, [browsePath])
return (
<Dialog
open={open}
onOpenChange={(next) => {
if (next) setBrowsePath(MAIL_DRIVE_DEFAULT_FOLDER)
onOpenChange(next)
}}
>
<DialogContent
overlayClassName={DRIVE_DIALOG_OVERLAY}
className={cn(DRIVE_DIALOG_CONTENT, "sm:max-w-[420px]")}
>
<DialogHeader className={cn("border-b px-5 py-4 text-left", DRIVE_DIALOG_DIVIDER)}>
<DialogTitle className={cn("text-base font-medium", DRIVE_TEXT_TITLE)}>
{title}
</DialogTitle>
{description ? (
<DialogDescription className={cn("text-sm", DRIVE_TEXT_SECONDARY)}>
{description}
</DialogDescription>
) : (
<DialogDescription className="sr-only">{title}</DialogDescription>
)}
</DialogHeader>
<div className="flex min-h-[280px] flex-col">
<div
className={cn(
"flex flex-wrap items-center gap-1 border-b px-4 py-2 text-sm",
DRIVE_DIALOG_DIVIDER
)}
>
{crumbs.map((crumb, i) => (
<span key={crumb.path} className="flex min-w-0 items-center gap-1">
{i > 0 ? (
<ChevronRight className={cn("h-3.5 w-3.5 shrink-0", DRIVE_TEXT_SECONDARY)} />
) : null}
<button
type="button"
className={cn(
"truncate rounded px-1 py-0.5 hover:bg-accent",
i === crumbs.length - 1
? cn("font-medium", DRIVE_TEXT_PRIMARY)
: DRIVE_TEXT_SECONDARY
)}
onClick={() => setBrowsePath(crumb.path)}
>
{crumb.label}
</button>
</span>
))}
</div>
<div className="min-h-0 flex-1 overflow-y-auto py-1">
{list.isLoading ? (
<p className={cn("px-4 py-6 text-sm", DRIVE_TEXT_SECONDARY)}>Chargement</p>
) : folders.length === 0 ? (
<p className={cn("px-4 py-6 text-sm", DRIVE_TEXT_SECONDARY)}>
Enregistrer dans ce dossier
</p>
) : (
folders.map((folder) => (
<button
key={folder.path}
type="button"
className={cn(
"flex w-full items-center gap-3 px-4 py-2.5 text-left text-sm hover:bg-accent",
DRIVE_TEXT_PRIMARY
)}
onClick={() => setBrowsePath(normalizeDriveFolderPath(folder.path))}
>
<Folder className={cn("h-4 w-4 shrink-0", DRIVE_TEXT_SECONDARY)} />
<span className="min-w-0 flex-1 truncate">{displayFileName(folder.name)}</span>
<ChevronRight className={cn("h-4 w-4 shrink-0", DRIVE_TEXT_SECONDARY)} />
</button>
))
)}
</div>
</div>
<DialogFooter className={cn(DRIVE_DIALOG_FOOTER, "px-4 py-3")}>
<Button
type="button"
variant="ghost"
className={DRIVE_BTN_GHOST}
onClick={() => onOpenChange(false)}
>
Annuler
</Button>
<Button
type="button"
className={DRIVE_BTN_PRIMARY}
disabled={pending}
onClick={() => void onConfirm(browsePath)}
>
{pending ? "Enregistrement…" : confirmLabel}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}