sozsoft-platform/ui/src/views/admin/files/components/FileModals.tsx

256 lines
7 KiB
TypeScript
Raw Normal View History

2026-02-24 20:44:16 +00:00
import { forwardRef, useState, useEffect } from 'react'
import { Dialog, Button, Input, FormItem } from '@/components/ui'
import { FaTimes } from 'react-icons/fa'
import type { FileItem } from '@/types/fileManagement'
// 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 handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!folderName.trim()) {
setError('Folder name is required')
return
}
try {
await onCreate(folderName.trim())
setFolderName('')
setError('')
onClose()
} catch (err) {
setError('Failed to create folder')
}
}
const handleClose = () => {
setFolderName('')
setError('')
onClose()
}
return (
<Dialog isOpen={isOpen} onClose={handleClose}>
<div ref={ref}>
<form onSubmit={handleSubmit} className="py-6">
<FormItem label="Folder Name" invalid={!!error} errorMessage={error}>
<Input
value={folderName}
onChange={(e) => setFolderName(e.target.value)}
placeholder="Enter folder name"
autoFocus
className="mt-2"
/>
</FormItem>
</form>
<div className="flex justify-end space-x-2 pt-4 border-t">
<Button variant="default" onClick={handleClose} disabled={loading}>
Cancel
</Button>
<Button
variant="solid"
onClick={handleSubmit}
loading={loading}
disabled={!folderName.trim()}
>
Create Folder
</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('')
// Update input when item changes
useEffect(() => {
if (item) {
setNewName(item.name)
}
}, [item])
const handleSubmit = async (e: React.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 (err) {
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">
Rename {item.type === 'folder' ? 'Folder' : 'File'}
</h3>
<Button variant="plain" size="sm" icon={<FaTimes />} onClick={handleClose} />
</div>
<form onSubmit={handleSubmit} className="py-6">
<FormItem
label={`${item.type === 'folder' ? 'Folder' : 'File'} Name`}
invalid={!!error}
errorMessage={error}
>
<Input
value={newName}
onChange={(e) => setNewName(e.target.value)}
placeholder={`Enter ${item.type} name`}
autoFocus
/>
</FormItem>
</form>
<div className="flex justify-end space-x-2 pt-4 border-t">
<Button variant="default" onClick={handleClose} disabled={loading}>
Cancel
</Button>
<Button
variant="solid"
onClick={handleSubmit}
loading={loading}
disabled={!newName.trim()}
>
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 handleDelete = async () => {
try {
await onDelete()
onClose()
} catch (err) {
// 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">Delete Items</h3>
</div>
<div className="py-6">
<p className="text-gray-900 dark:text-gray-100 mb-4">
Are you sure you want to delete the following items? This action cannot be undone.
</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 variant="default" onClick={onClose} disabled={loading}>
Cancel
</Button>
<Button
variant="solid"
className="bg-red-600 hover:bg-red-700 text-white"
onClick={handleDelete}
loading={loading}
>
Delete
</Button>
</div>
</div>
</Dialog>
)
},
)
DeleteConfirmModal.displayName = 'DeleteConfirmModal'