260 lines
7.4 KiB
TypeScript
260 lines
7.4 KiB
TypeScript
import { forwardRef, useState, useEffect, type FormEvent } from 'react'
|
|
import { Dialog, Button, Input, FormItem } from '@/components/ui'
|
|
import type { FileItem } from '@/types/fileManagement'
|
|
import { useLocalization } from '@/utils/hooks/useLocalization'
|
|
|
|
// Create Folder Modal
|
|
export interface CreateFolderModalProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
onCreate: (name: string) => Promise<void>
|
|
loading?: boolean
|
|
currentFolderId?: string
|
|
}
|
|
|
|
export const CreateFolderModal = forwardRef<HTMLDivElement, CreateFolderModalProps>(
|
|
(props, ref) => {
|
|
const { isOpen, onClose, onCreate, loading = false } = props
|
|
const [folderName, setFolderName] = useState('')
|
|
const [error, setError] = useState('')
|
|
const { translate } = useLocalization()
|
|
|
|
const handleSubmit = async (e: FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
if (!folderName.trim()) {
|
|
setError('Folder name is required')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await onCreate(folderName.trim())
|
|
setFolderName('')
|
|
setError('')
|
|
onClose()
|
|
} catch {
|
|
setError('Failed to create folder')
|
|
}
|
|
}
|
|
|
|
const handleClose = () => {
|
|
setFolderName('')
|
|
setError('')
|
|
onClose()
|
|
}
|
|
|
|
return (
|
|
<Dialog isOpen={isOpen} onClose={handleClose}>
|
|
<div ref={ref}>
|
|
<form className="py-6" onSubmit={handleSubmit}>
|
|
<FormItem label={translate('::FileManager.FolderName')} invalid={!!error} errorMessage={error}>
|
|
<Input
|
|
autoFocus
|
|
value={folderName}
|
|
className="mt-2"
|
|
onChange={(e) => setFolderName(e.target.value)}
|
|
/>
|
|
</FormItem>
|
|
</form>
|
|
|
|
<div className="flex justify-end space-x-2 pt-4 border-t">
|
|
<Button size='sm' variant="default" disabled={loading} onClick={handleClose}>
|
|
{translate('::Cancel')}
|
|
</Button>
|
|
<Button
|
|
size='sm'
|
|
variant="solid"
|
|
loading={loading}
|
|
disabled={!folderName.trim()}
|
|
onClick={handleSubmit}
|
|
>
|
|
{translate('::FileManager.CreateFolder')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
)
|
|
},
|
|
)
|
|
|
|
CreateFolderModal.displayName = 'CreateFolderModal'
|
|
|
|
// Rename Item Modal
|
|
export interface RenameItemModalProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
onRename: (newName: string) => Promise<void>
|
|
item?: FileItem
|
|
loading?: boolean
|
|
}
|
|
|
|
export const RenameItemModal = forwardRef<HTMLDivElement, RenameItemModalProps>((props, ref) => {
|
|
const { isOpen, onClose, onRename, item, loading = false } = props
|
|
const [newName, setNewName] = useState('')
|
|
const [error, setError] = useState('')
|
|
const { translate } = useLocalization()
|
|
|
|
// Update input when item changes
|
|
useEffect(() => {
|
|
if (item) {
|
|
setNewName(item.name)
|
|
}
|
|
}, [item])
|
|
|
|
const handleSubmit = async (e: FormEvent) => {
|
|
e.preventDefault()
|
|
|
|
if (!newName.trim()) {
|
|
setError(`${item?.type === 'folder' ? 'Folder' : 'File'} name is required`)
|
|
return
|
|
}
|
|
|
|
if (newName.trim() === item?.name) {
|
|
onClose()
|
|
return
|
|
}
|
|
|
|
try {
|
|
await onRename(newName.trim())
|
|
setError('')
|
|
onClose()
|
|
} catch {
|
|
setError(`Failed to rename ${item?.type}`)
|
|
}
|
|
}
|
|
|
|
const handleClose = () => {
|
|
setNewName('')
|
|
setError('')
|
|
onClose()
|
|
}
|
|
|
|
if (!item) return null
|
|
|
|
return (
|
|
<Dialog isOpen={isOpen} onClose={handleClose}>
|
|
<div ref={ref} className="max-w-md">
|
|
<div className="flex items-center justify-between pb-4 border-b">
|
|
<h3 className="text-lg font-semibold">
|
|
{translate(`::FileManager.${item.type === 'folder' ? 'RenameFolder' : 'RenameFile'}`)}
|
|
</h3>
|
|
</div>
|
|
|
|
<form className="py-6" onSubmit={handleSubmit}>
|
|
<FormItem
|
|
label={translate(`::FileManager.${item.type === 'folder' ? 'FolderName' : 'FileName'}`)}
|
|
invalid={!!error}
|
|
errorMessage={error}
|
|
>
|
|
<Input
|
|
autoFocus
|
|
value={newName}
|
|
placeholder={`Enter ${translate(`::FileManager.${item.type === 'folder' ? 'FolderName' : 'FileName'}`)}`}
|
|
onChange={(e) => setNewName(e.target.value)}
|
|
/>
|
|
</FormItem>
|
|
</form>
|
|
|
|
<div className="flex justify-end space-x-2 pt-4 border-t">
|
|
<Button size='sm' variant="default" disabled={loading} onClick={handleClose}>
|
|
{translate('::Cancel')}
|
|
</Button>
|
|
<Button
|
|
size='sm'
|
|
variant="solid"
|
|
loading={loading}
|
|
disabled={!newName.trim()}
|
|
onClick={handleSubmit}
|
|
>
|
|
{translate('::App.Listform.ListformField.Rename')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
)
|
|
})
|
|
|
|
RenameItemModal.displayName = 'RenameItemModal'
|
|
|
|
// Delete Confirmation Modal
|
|
export interface DeleteConfirmModalProps {
|
|
isOpen: boolean
|
|
onClose: () => void
|
|
onDelete: () => Promise<void>
|
|
items: FileItem[]
|
|
loading?: boolean
|
|
}
|
|
|
|
export const DeleteConfirmModal = forwardRef<HTMLDivElement, DeleteConfirmModalProps>(
|
|
(props, ref) => {
|
|
const { isOpen, onClose, onDelete, items, loading = false } = props
|
|
const { translate } = useLocalization()
|
|
|
|
const handleDelete = async () => {
|
|
try {
|
|
await onDelete()
|
|
onClose()
|
|
} catch {
|
|
// Error handling is done in parent component
|
|
}
|
|
}
|
|
|
|
const folderCount = items.filter((item) => item.type === 'folder').length
|
|
const fileCount = items.filter((item) => item.type === 'file').length
|
|
|
|
return (
|
|
<Dialog isOpen={isOpen} onClose={onClose}>
|
|
<div ref={ref}>
|
|
<div className="flex items-center justify-between pb-4 border-b">
|
|
<h3 className="text-lg font-semibold text-red-600">
|
|
{translate('::FileManager.DeleteConfirmationTitle')}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="py-6">
|
|
<p className="text-gray-900 dark:text-gray-100 mb-4">
|
|
{translate('::FileManager.DeleteConfirmationMessage')}
|
|
</p>
|
|
|
|
<div className="bg-gray-50 dark:bg-gray-700 rounded-lg p-2">
|
|
{folderCount > 0 && (
|
|
<p className="text-sm text-gray-700 dark:text-gray-300">
|
|
{folderCount} folder{folderCount > 1 ? 's' : ''}
|
|
</p>
|
|
)}
|
|
{fileCount > 0 && (
|
|
<p className="text-sm text-gray-700 dark:text-gray-300">
|
|
{fileCount} file{fileCount > 1 ? 's' : ''}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{items.length === 1 && (
|
|
<div className="mt-4">
|
|
<p className="text-sm font-medium text-gray-900 dark:text-gray-100">
|
|
{items[0].name}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className="flex justify-end space-x-2 pt-4 border-t">
|
|
<Button size='sm' variant="default" disabled={loading} onClick={onClose}>
|
|
{translate('::Cancel')}
|
|
</Button>
|
|
<Button
|
|
size='sm'
|
|
variant="solid"
|
|
loading={loading}
|
|
onClick={handleDelete}
|
|
>
|
|
{translate('::Delete')}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Dialog>
|
|
)
|
|
},
|
|
)
|
|
|
|
DeleteConfirmModal.displayName = 'DeleteConfirmModal'
|