bottom-end
This commit is contained in:
parent
f4d2be65e5
commit
9cd49f3f66
12 changed files with 254 additions and 183 deletions
|
|
@ -227,6 +227,9 @@ export class SignalRService {
|
|||
title="⚠️ Bağlantı yok. Sınıfa katılmadan önce bağlantıyı kontrol edin."
|
||||
type="warning"
|
||||
/>,
|
||||
{
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ export class WebRTCService {
|
|||
title={`⚠️ Bağlantı başarısız, yeniden deneniyor (${retries + 1}/${this.maxRetries})`}
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
this.retryCounts.set(userId, retries + 1)
|
||||
await this.restartIce(peerConnection, userId)
|
||||
|
|
@ -122,6 +123,7 @@ export class WebRTCService {
|
|||
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -201,6 +203,7 @@ export class WebRTCService {
|
|||
title={`⚠️ ICE candidate eklenemedi. Kullanıcı: ${userId}`}
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ const FileManager = () => {
|
|||
setItems(items)
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch items:', error)
|
||||
toast.push(<Notification type="danger">Failed to load files and folders</Notification>)
|
||||
toast.push(<Notification type="danger">Failed to load files and folders</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
|
|
@ -288,10 +290,14 @@ const FileManager = () => {
|
|||
await fileManagementService.uploadFileDirectly(formData, selectedTenant?.id)
|
||||
}
|
||||
await fetchItems(currentFolderId)
|
||||
toast.push(<Notification type="success">Files uploaded successfully</Notification>)
|
||||
toast.push(<Notification type="success">Files uploaded successfully</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Upload failed:', error)
|
||||
toast.push(<Notification type="danger">Failed to upload files</Notification>)
|
||||
toast.push(<Notification type="danger">Failed to upload files</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
setUploading(false)
|
||||
|
|
@ -309,10 +315,14 @@ const FileManager = () => {
|
|||
selectedTenant?.id,
|
||||
)
|
||||
await fetchItems(currentFolderId)
|
||||
toast.push(<Notification type="success">Folder created successfully</Notification>)
|
||||
toast.push(<Notification type="success">Folder created successfully</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Create folder failed:', error)
|
||||
toast.push(<Notification type="danger">Failed to create folder</Notification>)
|
||||
toast.push(<Notification type="danger">Failed to create folder</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
setCreating(false)
|
||||
|
|
@ -332,10 +342,14 @@ const FileManager = () => {
|
|||
selectedTenant?.id,
|
||||
)
|
||||
await fetchItems(currentFolderId)
|
||||
toast.push(<Notification type="success">Item renamed successfully</Notification>)
|
||||
toast.push(<Notification type="success">Item renamed successfully</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Rename failed:', error)
|
||||
toast.push(<Notification type="danger">Failed to rename item</Notification>)
|
||||
toast.push(<Notification type="danger">Failed to rename item</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
setRenaming(false)
|
||||
|
|
@ -357,10 +371,14 @@ const FileManager = () => {
|
|||
|
||||
await fetchItems(currentFolderId)
|
||||
setSelectedItems([])
|
||||
toast.push(<Notification type="success">Items deleted successfully</Notification>)
|
||||
toast.push(<Notification type="success">Items deleted successfully</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Delete failed:', error)
|
||||
toast.push(<Notification type="danger">Failed to delete items</Notification>)
|
||||
toast.push(<Notification type="danger">Failed to delete items</Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
throw error
|
||||
} finally {
|
||||
setDeleting(false)
|
||||
|
|
@ -399,6 +417,7 @@ const FileManager = () => {
|
|||
<Notification title="Warning" type="warning">
|
||||
Protected system folders cannot be deleted.
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -411,6 +430,7 @@ const FileManager = () => {
|
|||
<Notification title="Security Warning" type="warning">
|
||||
Folder {item.name} contains files and cannot be deleted for security reasons.
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -550,6 +570,7 @@ const FileManager = () => {
|
|||
{protectedItems.length} protected system folder(s) cannot be deleted:{' '}
|
||||
{protectedItems.map((i) => i.name).join(', ')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -559,6 +580,7 @@ const FileManager = () => {
|
|||
{foldersWithFiles.length} folder(s) containing files cannot be deleted for security
|
||||
reasons: {foldersWithFiles.join(', ')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -573,6 +595,7 @@ const FileManager = () => {
|
|||
<Notification title="Info" type="info">
|
||||
No items can be deleted. Selected items are either protected or folders containing files.
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -588,6 +611,7 @@ const FileManager = () => {
|
|||
{protectedItems.length} protected system folder(s) cannot be copied:{' '}
|
||||
{protectedItems.map((i) => i.name).join(', ')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -606,6 +630,7 @@ const FileManager = () => {
|
|||
<Notification title="Copied" type="success">
|
||||
{copyableItems.length} item(s) copied to clipboard
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -621,6 +646,7 @@ const FileManager = () => {
|
|||
{protectedItems.length} protected system folder(s) cannot be moved:{' '}
|
||||
{protectedItems.map((i) => i.name).join(', ')}
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -639,6 +665,7 @@ const FileManager = () => {
|
|||
<Notification title="Cut" type="success">
|
||||
{cuttableItems.length} item(s) cut to clipboard
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -650,6 +677,7 @@ const FileManager = () => {
|
|||
<Notification title="Clipboard Empty" type="info">
|
||||
No items in clipboard
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -667,6 +695,7 @@ const FileManager = () => {
|
|||
<Notification title={translate('::App.Platform.Success')} type="success">
|
||||
{itemIds.length} item(s) copied successfully
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Copy failed:', error)
|
||||
|
|
@ -674,6 +703,7 @@ const FileManager = () => {
|
|||
<Notification title={translate('::App.Platform.Error')} type="danger">
|
||||
Failed to copy items
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
|
@ -685,6 +715,7 @@ const FileManager = () => {
|
|||
<Notification title="Warning" type="warning">
|
||||
Cannot move items to the same folder
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -700,6 +731,7 @@ const FileManager = () => {
|
|||
<Notification title={translate('::App.Platform.Success')} type="success">
|
||||
{itemIds.length} item(s) moved successfully
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Move failed:', error)
|
||||
|
|
@ -707,6 +739,7 @@ const FileManager = () => {
|
|||
<Notification title={translate('::App.Platform.Error')} type="danger">
|
||||
Failed to move items
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
|
@ -717,6 +750,7 @@ const FileManager = () => {
|
|||
<Notification title="Error" type="danger">
|
||||
Invalid clipboard data
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -812,6 +846,7 @@ const FileManager = () => {
|
|||
<Notification title="Warning" type="warning">
|
||||
Protected system folders cannot be renamed
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -841,6 +876,7 @@ const FileManager = () => {
|
|||
<Notification title="Warning" type="warning">
|
||||
Only files can be downloaded
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -856,7 +892,8 @@ const FileManager = () => {
|
|||
title={translate('::FileManager.DeleteSelectedItems')}
|
||||
onClick={deleteSelectedItems}
|
||||
>
|
||||
<span>{translate('::Delete')}</span> {selectedItems.length > 0 && `(${selectedItems.length})`}
|
||||
<span>{translate('::Delete')}</span>{' '}
|
||||
{selectedItems.length > 0 && `(${selectedItems.length})`}
|
||||
</Button>
|
||||
|
||||
{/* Selection Actions */}
|
||||
|
|
@ -1044,10 +1081,18 @@ const FileManager = () => {
|
|||
{/* List View Header */}
|
||||
{viewMode === 'list' && (
|
||||
<div className="hidden sm:grid grid-cols-12 gap-4 px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400 border-b dark:border-gray-700 mb-2">
|
||||
<div className="col-span-5 lg:col-span-5">{translate('::App.Listform.ListformField.Name')}</div>
|
||||
<div className="col-span-2 lg:col-span-2">{translate('::App.Listform.ListformField.Type')}</div>
|
||||
<div className="col-span-2 lg:col-span-2">{translate('::App.Listform.ListformField.Size')}</div>
|
||||
<div className="col-span-2 lg:col-span-2">{translate('::App.Listform.ListformField.Modified')}</div>
|
||||
<div className="col-span-5 lg:col-span-5">
|
||||
{translate('::App.Listform.ListformField.Name')}
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-2">
|
||||
{translate('::App.Listform.ListformField.Type')}
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-2">
|
||||
{translate('::App.Listform.ListformField.Size')}
|
||||
</div>
|
||||
<div className="col-span-2 lg:col-span-2">
|
||||
{translate('::App.Listform.ListformField.Modified')}
|
||||
</div>
|
||||
<div className="col-span-1"></div> {/* Actions column */}
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -1100,6 +1145,7 @@ const FileManager = () => {
|
|||
<Notification title="Cut" type="success">
|
||||
Item ready to move. Navigate to target folder and paste.
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}}
|
||||
|
|
@ -1115,6 +1161,7 @@ const FileManager = () => {
|
|||
<Notification type="info">
|
||||
Image preview feature will be added soon.
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} else {
|
||||
// Diğer dosya tipleri için download
|
||||
|
|
|
|||
|
|
@ -107,11 +107,15 @@ const getFileTypeLabel = (item: FileItemType, translate: (key: string) => string
|
|||
if (mimeType?.startsWith('audio/')) return translate('::FileManager.Sound') // 'Ses'
|
||||
if (mimeType?.includes('pdf')) return translate('::FileManager.PDF') // 'PDF'
|
||||
if (mimeType?.includes('word')) return translate('::FileManager.Word') // 'Word'
|
||||
if (mimeType?.includes('excel') || mimeType?.includes('spreadsheet')) return translate('::FileManager.Excel') // 'Excel'
|
||||
if (mimeType?.includes('powerpoint') || mimeType?.includes('presentation')) return translate('::FileManager.PowerPoint') // 'PowerPoint'
|
||||
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension || '')) return translate('::FileManager.Archive') // 'Arşiv'
|
||||
if (mimeType?.includes('excel') || mimeType?.includes('spreadsheet'))
|
||||
return translate('::FileManager.Excel') // 'Excel'
|
||||
if (mimeType?.includes('powerpoint') || mimeType?.includes('presentation'))
|
||||
return translate('::FileManager.PowerPoint') // 'PowerPoint'
|
||||
if (['zip', 'rar', '7z', 'tar', 'gz'].includes(extension || ''))
|
||||
return translate('::FileManager.Archive') // 'Arşiv'
|
||||
if (['txt', 'md'].includes(extension || '')) return translate('::FileManager.Text') // 'Metin'
|
||||
if (['json', 'xml', 'css', 'js', 'ts', 'html'].includes(extension || '')) return translate('::FileManager.Code') // 'Kod'
|
||||
if (['json', 'xml', 'css', 'js', 'ts', 'html'].includes(extension || ''))
|
||||
return translate('::FileManager.Code') // 'Kod'
|
||||
|
||||
return extension?.toUpperCase() || translate('::FileManager.File')
|
||||
}
|
||||
|
|
@ -134,7 +138,9 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
} = props
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(null)
|
||||
const [contextMenuPosition, setContextMenuPosition] = useState<{ x: number; y: number } | null>(
|
||||
null,
|
||||
)
|
||||
const { translate } = useLocalization()
|
||||
const handleClick = () => {
|
||||
onSelect?.(item)
|
||||
|
|
@ -147,7 +153,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
const handleContextMenu = (e: MouseEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
|
||||
// Only show context menu if there are actions available
|
||||
if (actionMenuItems.length > 0) {
|
||||
setContextMenuPosition({ x: e.clientX, y: e.clientY })
|
||||
|
|
@ -217,6 +223,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
<Notification type="success" title="Başarılı">
|
||||
Dosya URL'si panoya kopyalandı
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
},
|
||||
() => {
|
||||
|
|
@ -224,6 +231,7 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
<Notification type="danger" title="Hata">
|
||||
URL kopyalanamadı
|
||||
</Notification>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -279,12 +287,16 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
]
|
||||
|
||||
const dropdownList = (
|
||||
<div
|
||||
<div
|
||||
className="fixed z-50 py-1 min-w-36 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg"
|
||||
style={contextMenuPosition ? {
|
||||
left: `${contextMenuPosition.x}px`,
|
||||
top: `${contextMenuPosition.y}px`,
|
||||
} : undefined}
|
||||
style={
|
||||
contextMenuPosition
|
||||
? {
|
||||
left: `${contextMenuPosition.x}px`,
|
||||
top: `${contextMenuPosition.y}px`,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{actionMenuItems.map((menuItem) => (
|
||||
<div
|
||||
|
|
@ -359,83 +371,81 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
onDoubleClick={handleDoubleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
{/* File Name */}
|
||||
<div className="col-span-5 flex items-center min-w-0 gap-2">
|
||||
{/* Checkbox */}
|
||||
{!item.isReadOnly ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
onChange={handleCheckboxChange}
|
||||
onClick={handleCheckboxClick}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-4 h-4" /> // Boş alan bırak
|
||||
)}
|
||||
|
||||
{/* File Icon or Preview */}
|
||||
<div className="w-8 h-8">
|
||||
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
||||
<ImagePreview
|
||||
src={FILE_URL(item.path, item.tenantId)}
|
||||
alt={item.name}
|
||||
{/* File Name */}
|
||||
<div className="col-span-5 flex items-center min-w-0 gap-2">
|
||||
{/* Checkbox */}
|
||||
{!item.isReadOnly ? (
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
|
||||
onChange={handleCheckboxChange}
|
||||
onClick={handleCheckboxClick}
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
{getFileIcon(item, false)}
|
||||
</div>
|
||||
<div className="w-4 h-4" /> // Boş alan bırak
|
||||
)}
|
||||
|
||||
{/* File Icon or Preview */}
|
||||
<div className="w-8 h-8">
|
||||
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
||||
<ImagePreview src={FILE_URL(item.path, item.tenantId)} alt={item.name} />
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
{getFileIcon(item, false)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p
|
||||
className="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate cursor-pointer hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
||||
title={item.name}
|
||||
>
|
||||
{item.name}
|
||||
</p>
|
||||
{item.isReadOnly && (
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300 flex-shrink-0">
|
||||
<span className="hidden sm:inline">Protected</span>
|
||||
<span className="sm:hidden">!</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p
|
||||
className="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate cursor-pointer hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
||||
title={item.name}
|
||||
>
|
||||
{item.name}
|
||||
</p>
|
||||
{item.isReadOnly && (
|
||||
<span className="inline-flex items-center px-1.5 py-0.5 rounded-full text-xs font-medium bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-300 flex-shrink-0">
|
||||
<span className="hidden sm:inline">Protected</span>
|
||||
<span className="sm:hidden">!</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File Type - Hidden on mobile */}
|
||||
<div className="hidden sm:flex col-span-2 items-center">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">{getFileTypeLabel(item, translate)}</span>
|
||||
</div>
|
||||
|
||||
{/* File Size / Folder Item Count */}
|
||||
<div className="col-span-2 sm:col-span-2 flex items-center">
|
||||
{item.type === 'file' && item.size ? (
|
||||
{/* File Type - Hidden on mobile */}
|
||||
<div className="hidden sm:flex col-span-2 items-center">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{formatFileSize(item.size)}
|
||||
{getFileTypeLabel(item, translate)}
|
||||
</span>
|
||||
) : item.type === 'folder' && typeof item.childCount === 'number' ? (
|
||||
</div>
|
||||
|
||||
{/* File Size / Folder Item Count */}
|
||||
<div className="col-span-2 sm:col-span-2 flex items-center">
|
||||
{item.type === 'file' && item.size ? (
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{formatFileSize(item.size)}
|
||||
</span>
|
||||
) : item.type === 'folder' && typeof item.childCount === 'number' ? (
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{item.childCount} öğe
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">-</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modified Date - Hidden on mobile */}
|
||||
<div className="hidden sm:flex col-span-2 items-center">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{item.childCount} öğe
|
||||
{formatDate(item.modifiedAt)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">-</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Action Menu - Removed */}
|
||||
<div className="col-span-1 sm:col-span-1 flex items-center justify-end"></div>
|
||||
</div>
|
||||
|
||||
{/* Modified Date - Hidden on mobile */}
|
||||
<div className="hidden sm:flex col-span-2 items-center">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{formatDate(item.modifiedAt)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Action Menu - Removed */}
|
||||
<div className="col-span-1 sm:col-span-1 flex items-center justify-end">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Context Menu */}
|
||||
{dropdownOpen && contextMenuPosition && dropdownList}
|
||||
{/* Context Menu */}
|
||||
{dropdownOpen && contextMenuPosition && dropdownList}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -457,69 +467,68 @@ const FileItem = forwardRef<HTMLDivElement, FileItemProps>((props, ref) => {
|
|||
onDoubleClick={handleDoubleClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
{/* Checkbox */}
|
||||
{!item.isReadOnly && (
|
||||
<div className="absolute top-3 left-3 z-10">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
className="w-4 h-4 text-blue-600 bg-white border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 shadow-sm"
|
||||
onChange={handleCheckboxChange}
|
||||
onClick={handleCheckboxClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
|
||||
{/* File/Folder Icon or Preview */}
|
||||
<div className="flex justify-center mb-3">
|
||||
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
||||
<div className="w-16 h-16">
|
||||
<ImagePreview
|
||||
src={FILE_URL(item.path, item.tenantId)}
|
||||
alt={item.name}
|
||||
{/* Checkbox */}
|
||||
{!item.isReadOnly && (
|
||||
<div className="absolute top-3 left-3 z-10">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selected}
|
||||
className="w-4 h-4 text-blue-600 bg-white border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600 shadow-sm"
|
||||
onChange={handleCheckboxChange}
|
||||
onClick={handleCheckboxClick}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center">{getFileIcon(item, true)}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File/Folder Name and Details */}
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center gap-1 mb-1">
|
||||
<p
|
||||
className="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate cursor-pointer hover:text-blue-600 dark:hover:text-blue-400 transition-colors px-2"
|
||||
title={item.name}
|
||||
>
|
||||
{item.name}
|
||||
</p>
|
||||
{/* File/Folder Icon or Preview */}
|
||||
<div className="flex justify-center mb-3">
|
||||
{item.type === 'file' && item.mimeType?.startsWith('image/') ? (
|
||||
<div className="w-16 h-16">
|
||||
<ImagePreview src={FILE_URL(item.path, item.tenantId)} alt={item.name} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex justify-center">{getFileIcon(item, true)}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File Size and Type */}
|
||||
{item.type === 'file' && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{getFileTypeLabel(item, translate)}</p>
|
||||
{item.size && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{formatFileSize(item.size)}
|
||||
</p>
|
||||
)}
|
||||
{/* File/Folder Name and Details */}
|
||||
<div className="text-center">
|
||||
<div className="flex items-center justify-center gap-1 mb-1">
|
||||
<p
|
||||
className="text-sm font-semibold text-gray-900 dark:text-gray-100 truncate cursor-pointer hover:text-blue-600 dark:hover:text-blue-400 transition-colors px-2"
|
||||
title={item.name}
|
||||
>
|
||||
{item.name}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Folder Child Count */}
|
||||
{item.type === 'folder' && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{typeof item.childCount === 'number' ? `${item.childCount} ${translate('::App.Listform.ListformField.Item')}` : translate('::FileManager.Counting')}
|
||||
</p>
|
||||
)}
|
||||
{/* File Size and Type */}
|
||||
{item.type === 'file' && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{getFileTypeLabel(item, translate)}
|
||||
</p>
|
||||
{item.size && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{formatFileSize(item.size)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Folder Child Count */}
|
||||
{item.type === 'folder' && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{typeof item.childCount === 'number'
|
||||
? `${item.childCount} ${translate('::App.Listform.ListformField.Item')}`
|
||||
: translate('::FileManager.Counting')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Context Menu */}
|
||||
{dropdownOpen && contextMenuPosition && dropdownList}
|
||||
|
||||
{/* Context Menu */}
|
||||
{dropdownOpen && contextMenuPosition && dropdownList}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -894,7 +894,7 @@ function JsonRowOpDialogEditForm({
|
|||
</Button>
|
||||
<div className="flex gap-2">
|
||||
<Button size="sm" variant="plain" onClick={handleClose}>
|
||||
{ translate('::Cancel') }
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
{isSubmitting ? translate('::Saving') : translate('::Save')}
|
||||
|
|
@ -912,7 +912,9 @@ function JsonRowOpDialogEditForm({
|
|||
setSubmitting(true)
|
||||
try {
|
||||
await deleteListFormJsonRow(data.id, data.tabName, values.index)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>)
|
||||
toast.push(<Notification type="success">Kayıt silindi </Notification>, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
handleClose()
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
|
|
@ -935,7 +937,7 @@ function JsonRowOpDialogEditForm({
|
|||
</Dialog.Body>
|
||||
<Dialog.Footer className="flex justify-end gap-2 border-t pt-3 mt-1">
|
||||
<Button size="sm" variant="plain" onClick={handleClose}>
|
||||
{ translate('::Cancel') }
|
||||
{translate('::Cancel')}
|
||||
</Button>
|
||||
<Button size="sm" variant="solid" loading={isSubmitting} type="submit">
|
||||
{isSubmitting ? 'Deleting' : 'Delete'}
|
||||
|
|
|
|||
|
|
@ -344,7 +344,9 @@ function MenuTreeInline({
|
|||
onReload()
|
||||
setEditingCode(null)
|
||||
} catch (e: any) {
|
||||
toast.push(<Notification title={e.message} type="danger" />, { placement: 'bottom-end' })
|
||||
toast.push(<Notification title={e.message} type="danger" />, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
|
|
@ -363,7 +365,9 @@ function MenuTreeInline({
|
|||
await menuService.delete(node.id)
|
||||
onReload()
|
||||
} catch (e: any) {
|
||||
toast.push(<Notification title={e.message} type="danger" />, { placement: 'bottom-end' })
|
||||
toast.push(<Notification title={e.message} type="danger" />, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import {
|
|||
getVideoroomAttandances,
|
||||
getVideoroomById,
|
||||
getVideoroomChats,
|
||||
endVideoroom
|
||||
endVideoroom,
|
||||
} from '@/services/videoroom.service'
|
||||
import { showDbDateAsIs } from '@/utils/dateUtils'
|
||||
import { useNavigate, useParams } from 'react-router-dom'
|
||||
|
|
@ -665,6 +665,7 @@ const RoomDetail = () => {
|
|||
title="⚠️ Mikrofon alınamadı. Sadece ekran paylaşımı yapılacak."
|
||||
type="warning"
|
||||
/>,
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ const SqlQueryManager = () => {
|
|||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{translate('::App.Platform.FailedtoloadDatasources')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -551,7 +551,7 @@ GO`,
|
|||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseSelectDataSource')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -565,7 +565,7 @@ GO`,
|
|||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseEnterQuery')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -614,7 +614,7 @@ GO`,
|
|||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || translate('::App.Platform.FailedToExecuteQuery')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -643,7 +643,7 @@ GO`,
|
|||
{error.response?.data?.error?.message ||
|
||||
translate('::App.Platform.FailedToLoadDefinition')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -659,7 +659,7 @@ GO`,
|
|||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.ScriptNotGenerated') || 'Tablo scripti olusturulamadi.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -676,14 +676,14 @@ GO`,
|
|||
<Notification type="success" title={translate('::App.Platform.Success') || 'Basarili'}>
|
||||
{'Script Query Editor alanina yuklendi.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || 'Tablo scripti olusturulurken hata olustu.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -813,7 +813,7 @@ GO`,
|
|||
{translate('::App.Platform.CopyCompleted') ||
|
||||
`translate('::App.Platform.Successful'): ${successCount}, ${translate('::App.Platform.Error')}: ${errorCount}, ${translate('::App.Platform.Skipped')}: ${skippedCount}`}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
||||
setCopyResults(results)
|
||||
|
|
@ -831,7 +831,7 @@ GO`,
|
|||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseEnterQuery')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -841,7 +841,7 @@ GO`,
|
|||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
{translate('::App.Platform.PleaseSelectAtLeastOneTarget')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -890,7 +890,7 @@ GO`,
|
|||
{translate('::App.Platform.ExecutionCompleted') ||
|
||||
`translate('::App.Platform.Successful'): ${successCount}, ${translate('::App.Platform.Error')}: ${errorCount}`}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
|
||||
setCopyResults(results)
|
||||
|
|
@ -946,7 +946,7 @@ GO`,
|
|||
translate('::App.Platform.FailedToLoadFiles') ||
|
||||
'SQL dosya listesi yuklenemedi.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} finally {
|
||||
setIsLoadingSqlDataFiles(false)
|
||||
|
|
@ -1014,14 +1014,14 @@ GO`,
|
|||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{translate('::App.Platform.OperationCompleted') || 'Dosya tasindi.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || 'Dosya tasinamadi.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} finally {
|
||||
setIsMovingSqlDataFile(false)
|
||||
|
|
@ -1050,7 +1050,7 @@ GO`,
|
|||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || error.message || 'Record operation failed.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
|
@ -1083,14 +1083,14 @@ GO`,
|
|||
{translate('::App.SqlQueryManager.ScriptLoadedToEditor') ||
|
||||
'SQL dosyasi Query Editor icine yuklendi.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.push(
|
||||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{error.response?.data?.error?.message || 'SQL dosyasi okunamadi.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} finally {
|
||||
setIsPreviewingSqlDataFile(false)
|
||||
|
|
|
|||
|
|
@ -1298,7 +1298,7 @@ const SqlTableDesignerDialog = ({
|
|||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
CREATE TABLE script bulunamadı. Scripti panoya kopyalayıp tekrar deneyin.
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -1309,7 +1309,7 @@ const SqlTableDesignerDialog = ({
|
|||
<Notification type="warning" title={translate('::App.Platform.Warning')}>
|
||||
CREATE TABLE scriptinden sütunlar ayrıştırılamadı.
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -1348,7 +1348,7 @@ const SqlTableDesignerDialog = ({
|
|||
? `${addedCount} sütun eklendi${skippedCount > 0 ? `, ${skippedCount} sütun zaten mevcut olduğu için atlandı.` : '.'}`
|
||||
: 'Yeni sütun eklenmedi. Scriptteki sütunların tamamı zaten mevcut.'}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1504,7 +1504,7 @@ const SqlTableDesignerDialog = ({
|
|||
<Notification type="warning" title={translate('::App.SqlQueryManager.Warning')}>
|
||||
Referans kolon PK/UNIQUE olmalı. Lütfen hedef tablodan anahtar bir kolon seçin.
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -1602,7 +1602,7 @@ const SqlTableDesignerDialog = ({
|
|||
<Notification type="warning" title={translate('::App.SqlQueryManager.Warning')}>
|
||||
{translate('::App.SqlQueryManager.PleaseSelectDataSource')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
@ -1625,7 +1625,7 @@ const SqlTableDesignerDialog = ({
|
|||
<Notification type="success" title={translate('::App.Platform.Success')}>
|
||||
{`${translate(isEditMode ? '::App.SqlQueryManager.TableUpdated' : '::App.SqlQueryManager.TableCreated')}: [dbo].[${deployedTable}]`}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
// Save seed file to DbMigrator/Seeds/SqlData after successful deploy.
|
||||
// Always use the full CREATE TABLE script (not the ALTER diff) so the seed file
|
||||
|
|
@ -1649,7 +1649,7 @@ const SqlTableDesignerDialog = ({
|
|||
<Notification type="danger" title={translate('::App.Platform.Error')}>
|
||||
{result.data.message || translate('::App.SqlQueryManager.TableCreationFailed')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
}
|
||||
} catch (error: any) {
|
||||
|
|
@ -1658,7 +1658,7 @@ const SqlTableDesignerDialog = ({
|
|||
{error.response?.data?.error?.message ||
|
||||
translate('::App.SqlQueryManager.TableDeployFailed')}
|
||||
</Notification>,
|
||||
{ placement: 'top-center' },
|
||||
{ placement: 'bottom-end' },
|
||||
)
|
||||
} finally {
|
||||
setIsDeploying(false)
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ const useListFormCustomDataSource = ({
|
|||
// {'multiValue Error'}
|
||||
// </Notification>,
|
||||
// {
|
||||
// placement: 'top-end',
|
||||
// placement: 'bottom-end',
|
||||
// },
|
||||
// )
|
||||
console.log('multiValue Error', e)
|
||||
|
|
@ -297,7 +297,7 @@ const useListFormCustomDataSource = ({
|
|||
// {error.toString()}
|
||||
// </Notification>,
|
||||
// {
|
||||
// placement: 'top-end',
|
||||
// placement: 'bottom-end',
|
||||
// },
|
||||
// )
|
||||
return null
|
||||
|
|
@ -341,7 +341,7 @@ const useListFormCustomDataSource = ({
|
|||
// {error.toString()}
|
||||
// </Notification>,
|
||||
// {
|
||||
// placement: 'top-end',
|
||||
// placement: 'bottom-end',
|
||||
// },
|
||||
// )
|
||||
return null
|
||||
|
|
@ -364,7 +364,7 @@ const useListFormCustomDataSource = ({
|
|||
// {error.toString()}
|
||||
// </Notification>,
|
||||
// {
|
||||
// placement: 'top-end',
|
||||
// placement: 'bottom-end',
|
||||
// },
|
||||
// )
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ const Contact: React.FC = () => {
|
|||
{translate('::ListForms.FormBilgileriKaydedildi')}
|
||||
</Notification>,
|
||||
{
|
||||
placement: 'top-center',
|
||||
placement: 'bottom-end',
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -208,7 +208,9 @@ export function MenuAddDialog({
|
|||
})
|
||||
onClose()
|
||||
} catch (e: any) {
|
||||
toast.push(<Notification title={e.message} type="danger" />, { placement: 'bottom-end' })
|
||||
toast.push(<Notification title={e.message} type="danger" />, {
|
||||
placement: 'bottom-end',
|
||||
})
|
||||
} finally {
|
||||
setSaving(false)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue