sozsoft-platform/ui/src/views/admin/files/components/FileModals.tsx
2026-07-06 12:44:26 +03:00

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'