From 47d9a20998dc4e590c161554e078432c906a9a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96ZT=C3=9CRK?= <76204082+iamsedatozturk@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:17:07 +0300 Subject: [PATCH] =?UTF-8?q?Intranet=20=C3=BCzerinde=20Recent=20Documents?= =?UTF-8?q?=20komponenti=20yap=C4=B1ld=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileManagementAppService.cs | 25 +- .../Intranet/IntranetAppService.cs | 8 +- .../Migrations/LanguagesData.json | 108 +++- .../Migrations/MenusData.json | 21 + .../Migrations/PermissionsData.json | 36 ++ .../Seeds/TenantData.json | 4 +- .../intranet/documents/IntranetDocuments.tsx | 577 ++++++++++++++++++ .../views/intranet/documents/documentUtils.ts | 67 ++ .../{RecentDocuments.tsx => Documents.tsx} | 24 +- 9 files changed, 844 insertions(+), 26 deletions(-) create mode 100644 ui/src/views/intranet/documents/IntranetDocuments.tsx create mode 100644 ui/src/views/intranet/documents/documentUtils.ts rename ui/src/views/intranet/widgets/{RecentDocuments.tsx => Documents.tsx} (85%) diff --git a/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs b/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs index 31771fc1..bfa7232e 100644 --- a/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs +++ b/api/src/Sozsoft.Platform.Application/FileManagement/FileManagementAppService.cs @@ -16,6 +16,7 @@ using Sozsoft.Platform.Data.Seeds; using Sozsoft.Platform.Permissions; using Volo.Abp; using Volo.Abp.Application.Services; +using Volo.Abp.Authorization; using Volo.Abp.MultiTenancy; namespace Sozsoft.Platform.FileManagement; @@ -196,8 +197,28 @@ public class FileManagementAppService : ApplicationService, IFileManagementAppSe return ext.Trim().TrimStart('.').ToLowerInvariant(); } - private string GetEffectiveTenantId(string? inputTenantId) => - !string.IsNullOrEmpty(inputTenantId) ? inputTenantId : (_currentTenant.Id?.ToString() ?? "host"); + /// + /// Kapsam secimi yalnizca host baglaminda serbesttir. Tenant baglamindaki bir cagri + /// istekte baska bir tenant id gonderirse reddedilir; aksi halde istemci parametresi + /// tenant izolasyonunu asabilirdi. + /// + private string GetEffectiveTenantId(string? inputTenantId) + { + var currentTenantId = _currentTenant.Id?.ToString(); + + if (currentTenantId == null) + { + return !string.IsNullOrEmpty(inputTenantId) ? inputTenantId : HostFolderName; + } + + if (!string.IsNullOrEmpty(inputTenantId) && + !string.Equals(inputTenantId, currentTenantId, StringComparison.OrdinalIgnoreCase)) + { + throw new AbpAuthorizationException("Files of another tenant cannot be accessed."); + } + + return currentTenantId; + } private string EncodePathAsId(string path) { diff --git a/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs b/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs index 59f9dd6e..e3c7c586 100644 --- a/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs +++ b/api/src/Sozsoft.Platform.Application/Intranet/IntranetAppService.cs @@ -741,8 +741,12 @@ public class IntranetAppService : PlatformAppService, IIntranetAppService return items; } - var tenantId = _currentTenant.Id?.ToString() ?? "host"; - var fullPath = Path.Combine(cdnBasePath, tenantId); + // CDN kapsam duzeni File Manager ile ayni olmak zorunda: tenant dosyalari + // tenants/{tenantId} altina yazilir, host dosyalari host altina. + var tenantId = _currentTenant.Id?.ToString(); + var fullPath = tenantId == null + ? Path.Combine(cdnBasePath, "host") + : Path.Combine(cdnBasePath, "tenants", tenantId); if (!string.IsNullOrEmpty(folderPath)) { diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json index a52a82ee..296db7bc 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/LanguagesData.json @@ -12318,12 +12318,6 @@ "tr": "Bugünün doğum günleri", "en": "Today's birthdays" }, - { - "resourceName": "Platform", - "key": "App.DashboardWidget.Documents", - "tr": "Son dokümanlar", - "en": "Recent documents" - }, { "resourceName": "Platform", "key": "App.DashboardWidget.Surveys", @@ -19326,6 +19320,108 @@ "en": "Object", "tr": "Nesne" }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document", + "en": "Documents", + "tr": "Dokümanlar" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Active", + "en": "Published", + "tr": "Yayında" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Passive", + "en": "Unpublished", + "tr": "Yayında Değil" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Publish", + "en": "Publish", + "tr": "Yayına Al" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Unpublish", + "en": "Unpublish", + "tr": "Yayından Kaldır" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.DisplayName", + "en": "Display Name", + "tr": "Görünecek Ad" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.DisplayNameHint", + "en": "This name is saved as the file name and is shown in the Documents widget.", + "tr": "Bu ad dosya adı olarak kaydedilir ve Son Dokümanlar widget'ında görünür." + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.KanbanView", + "en": "Board view", + "tr": "Pano görünümü" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.ListView", + "en": "List view", + "tr": "Liste görünümü" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.ShowUnpublished", + "en": "Show unpublished", + "tr": "Yayında olmayanları göster" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Unknown", + "en": "Unknown", + "tr": "Bilinmiyor" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Empty", + "en": "There are no documents in this folder.", + "tr": "Bu klasörde doküman yok." + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.DeleteConfirm", + "en": "The selected document(s) will be permanently deleted. Do you confirm?", + "tr": "Seçili doküman(lar) kalıcı olarak silinecek. Onaylıyor musunuz?" + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Uploaded", + "en": "Document(s) uploaded.", + "tr": "Doküman(lar) yüklendi." + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.Deleted", + "en": "Document(s) deleted.", + "tr": "Doküman(lar) silindi." + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.StatusChanged", + "en": "Publication status updated.", + "tr": "Yayın durumu güncellendi." + }, + { + "resourceName": "Platform", + "key": "App.Intranet.Document.LoadFailed", + "en": "Documents could not be loaded.", + "tr": "Dokümanlar yüklenemedi." + }, { "resourceName": "Platform", "key": "App.Platform.Passive", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/MenusData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/MenusData.json index 73dcdfa1..e4411646 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/MenusData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/MenusData.json @@ -376,6 +376,16 @@ "routeType": "protected", "authority": ["App.DeveloperKit.Components"] }, + { + "key": "admin.intranet.documents", + "path": "/admin/intranet-documents", + "componentType": "normal", + "componentPath": "@/views/intranet/documents/IntranetDocuments", + "routeType": "protected", + "authority": [ + "App.Intranet.Document" + ] + }, { "key": "admin.fileManagement", "path": "/admin/files", @@ -1259,6 +1269,17 @@ "IsDisabled": false, "ModuleId": "Adm" }, + { + "ParentCode": "App.Administration.Intranet", + "Code": "App.Intranet.Document", + "DisplayName": "App.Intranet.Document", + "Order": 7, + "Url": "/admin/intranet-documents", + "Icon": "FcOpenedFolder", + "RequiredPermissionName": "App.Intranet.Document", + "IsDisabled": false, + "ModuleId": "Adm" + }, { "ParentCode": "App.Administration", "Code": "App.Administration.Restrictions", diff --git a/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json b/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json index 30ee4d36..323fa4ec 100644 --- a/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json +++ b/api/src/Sozsoft.Platform.DbMigrator/Migrations/PermissionsData.json @@ -3396,6 +3396,42 @@ "MultiTenancySide": 3, "Modules": "Sas|Adm" }, + { + "GroupName": "App.Administration", + "Name": "App.Intranet.Document", + "ParentName": "App.Intranet", + "DisplayName": "App.Intranet.Document", + "IsEnabled": true, + "MultiTenancySide": 3, + "Modules": "Sas|Adm" + }, + { + "GroupName": "App.Administration", + "Name": "App.Intranet.Document.Create", + "ParentName": "App.Intranet.Document", + "DisplayName": "App.Platform.Upload", + "IsEnabled": true, + "MultiTenancySide": 3, + "Modules": "Sas|Adm" + }, + { + "GroupName": "App.Administration", + "Name": "App.Intranet.Document.Update", + "ParentName": "App.Intranet.Document", + "DisplayName": "App.Platform.Update", + "IsEnabled": true, + "MultiTenancySide": 3, + "Modules": "Sas|Adm" + }, + { + "GroupName": "App.Administration", + "Name": "App.Intranet.Document.Delete", + "ParentName": "App.Intranet.Document", + "DisplayName": "App.Platform.Delete", + "IsEnabled": true, + "MultiTenancySide": 3, + "Modules": "Sas|Adm" + }, { "GroupName": "App.Administration", "Name": "App.Intranet.Announcement", diff --git a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json index f2f11cf8..1cffd581 100644 --- a/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json +++ b/api/src/Sozsoft.Platform.EntityFrameworkCore/Seeds/TenantData.json @@ -2255,8 +2255,8 @@ }, { "Code": "documents", - "Name": "Recent Documents", - "LabelKey": "::App.DashboardWidget.Documents", + "Name": "Documents", + "LabelKey": "::App.Intranet.Documents", "SourceType": "Builtin", "ComponentName": "", "PermissionName": "App.Files.Widget", diff --git a/ui/src/views/intranet/documents/IntranetDocuments.tsx b/ui/src/views/intranet/documents/IntranetDocuments.tsx new file mode 100644 index 00000000..10f25a62 --- /dev/null +++ b/ui/src/views/intranet/documents/IntranetDocuments.tsx @@ -0,0 +1,577 @@ +import { useCallback, useEffect, useMemo, useRef, useState, type ChangeEvent } from 'react' +import { + FaCloudUploadAlt, + FaEye, + FaEyeSlash, + FaList, + FaSearch, + FaSyncAlt, + FaTh, + FaTrash, +} from 'react-icons/fa' +import Container from '@/components/shared/Container' +import PageTitle from '@/components/shared/PageTitle' +import { Button, Checkbox, Dialog, Input, Notification, Spinner, Tag, toast } from '@/components/ui' +import { getFileIcon } from '@/proxy/intranet/utils' +import fileManagementService from '@/services/fileManagement.service' +import { useStoreState } from '@/store' +import { useLocalization } from '@/utils/hooks/useLocalization' +import { usePermission } from '@/utils/hooks/usePermission' +import type { FileItem } from '@/types/fileManagement' +import classNames from 'classnames' +import { + ACTIVE_FOLDER_ID, + PASSIVE_FOLDER_ID, + baseNameOf, + formatFileSize, + normalizeExtension, + splitFileName, + toneOf, + type IntranetDocument, + type PendingUpload, +} from './documentUtils' + +const TOAST_PLACEMENT = { placement: 'bottom-end' } as const + +const PERMISSION = { + UPLOAD: 'App.Intranet.Document.Create', + UPDATE: 'App.Intranet.Document.Update', + DELETE: 'App.Intranet.Document.Delete', +} as const + +type ViewMode = 'kanban' | 'list' + +interface DocumentColumn { + key: string + label: string + items: IntranetDocument[] +} + +const IntranetDocuments = () => { + const { translate } = useLocalization() + const { checkPermission } = usePermission() + const tenantId = useStoreState((state) => state.auth.tenant?.tenantId) + + const [documents, setDocuments] = useState([]) + const [loading, setLoading] = useState(true) + const [busy, setBusy] = useState(false) + const [viewMode, setViewMode] = useState('kanban') + const [searchTerm, setSearchTerm] = useState('') + const [showUnpublished, setShowUnpublished] = useState(true) + const [selectedIds, setSelectedIds] = useState([]) + const [pendingUploads, setPendingUploads] = useState([]) + const [uploadOpen, setUploadOpen] = useState(false) + const [deleteOpen, setDeleteOpen] = useState(false) + + const fileInputRef = useRef(null) + + const canUpload = checkPermission(PERMISSION.UPLOAD) + const canUpdate = checkPermission(PERMISSION.UPDATE) + const canDelete = checkPermission(PERMISSION.DELETE) + + const notify = useCallback((type: 'success' | 'danger' | 'warning', message: string) => { + toast.push({message}, TOAST_PLACEMENT) + }, []) + + const load = useCallback(async () => { + setLoading(true) + try { + const [published, unpublished] = await Promise.all([ + fileManagementService.getItems(ACTIVE_FOLDER_ID, tenantId), + fileManagementService.getItems(PASSIVE_FOLDER_ID, tenantId), + ]) + + const toDocuments = (items: FileItem[], isActive: boolean): IntranetDocument[] => + items.filter((item) => item.type === 'file').map((item) => ({ ...item, isActive })) + + setDocuments([ + ...toDocuments(published.data.items || [], true), + ...toDocuments(unpublished.data.items || [], false), + ]) + setSelectedIds([]) + } catch (error) { + console.error('Failed to load intranet documents:', error) + notify('danger', translate('::App.Intranet.Document.LoadFailed')) + setDocuments([]) + } finally { + setLoading(false) + } + }, [notify, tenantId, translate]) + + useEffect(() => { + load() + }, [load]) + + const visibleDocuments = useMemo(() => { + const term = searchTerm.trim().toLowerCase() + + return documents + .filter((item) => showUnpublished || item.isActive) + .filter((item) => !term || item.name.toLowerCase().includes(term)) + .sort((a, b) => new Date(b.modifiedAt).getTime() - new Date(a.modifiedAt).getTime()) + }, [documents, searchTerm, showUnpublished]) + + // Kolonlar klasördeki dosyalardan türer; yeni bir tür yüklendiğinde kolonu kendiliğinden açılır. + const columns = useMemo(() => { + const grouped = new Map() + + for (const item of visibleDocuments) { + const key = normalizeExtension(item.extension) + const column = grouped.get(key) + + if (column) { + column.items.push(item) + } else { + grouped.set(key, { + key, + label: key ? key.toUpperCase() : translate('::App.Intranet.Document.Unknown'), + items: [item], + }) + } + } + + return [...grouped.values()].sort( + (a, b) => b.items.length - a.items.length || a.label.localeCompare(b.label), + ) + }, [translate, visibleDocuments]) + + const selectedDocuments = useMemo( + () => documents.filter((item) => selectedIds.includes(item.id)), + [documents, selectedIds], + ) + const publishable = useMemo( + () => selectedDocuments.filter((item) => !item.isActive), + [selectedDocuments], + ) + const unpublishable = useMemo( + () => selectedDocuments.filter((item) => item.isActive), + [selectedDocuments], + ) + + const toggleSelection = useCallback((id: string) => { + setSelectedIds((previous) => + previous.includes(id) ? previous.filter((item) => item !== id) : [...previous, id], + ) + }, []) + + const handleFilesPicked = (event: ChangeEvent) => { + const files = Array.from(event.target.files || []) + // Aynı dosya art arda seçilebilsin diye input her seçimden sonra sıfırlanır. + event.target.value = '' + if (files.length === 0) return + + setPendingUploads( + files.map((file) => { + const { base, extension } = splitFileName(file.name) + return { file, name: base, extension } + }), + ) + setUploadOpen(true) + } + + const closeUpload = () => { + if (busy) return + setUploadOpen(false) + setPendingUploads([]) + } + + const uploadPendingFiles = async () => { + if (pendingUploads.some((item) => !item.name.trim())) { + notify('warning', translate('::App.Intranet.Document.DisplayNameHint')) + return + } + + setBusy(true) + try { + for (const item of pendingUploads) { + const formData = new FormData() + formData.append('fileName', `${item.name.trim()}${item.extension}`) + formData.append('parentId', ACTIVE_FOLDER_ID) + formData.append('Files', item.file) + + await fileManagementService.uploadFileDirectly(formData, tenantId) + } + + setUploadOpen(false) + setPendingUploads([]) + await load() + notify('success', translate('::App.Intranet.Document.Uploaded')) + } catch (error) { + console.error('Intranet document upload failed:', error) + notify('danger', translate('::App.Platform.Error')) + } finally { + setBusy(false) + } + } + + const changePublication = async (items: IntranetDocument[], publish: boolean) => { + if (items.length === 0) return + + setBusy(true) + try { + await fileManagementService.moveItems( + items.map((item) => item.id), + publish ? ACTIVE_FOLDER_ID : PASSIVE_FOLDER_ID, + tenantId, + ) + await load() + notify('success', translate('::App.Intranet.Document.StatusChanged')) + } catch (error) { + console.error('Failed to change publication state:', error) + notify('danger', translate('::App.Platform.Error')) + } finally { + setBusy(false) + } + } + + const deleteSelected = async () => { + setBusy(true) + try { + await fileManagementService.bulkDeleteItems(selectedIds, tenantId) + setDeleteOpen(false) + await load() + notify('success', translate('::App.Intranet.Document.Deleted')) + } catch (error) { + console.error('Failed to delete intranet documents:', error) + notify('danger', translate('::App.Platform.Error')) + } finally { + setBusy(false) + } + } + + const renderBadge = (document: IntranetDocument) => ( + + {getFileIcon(document.extension || '')} + + ) + + const renderStatus = (isActive: boolean) => ( + + {translate(isActive ? '::App.Intranet.Document.Active' : '::App.Intranet.Document.Passive')} + + ) + + const renderPublicationButton = (document: IntranetDocument) => + canUpdate ? ( + + )} + {canUpdate && ( + <> + + + + )} + {canDelete && ( + + )} + + +
+ + {translate('::App.Intranet.Document.ShowUnpublished')} + + } + size="sm" + value={searchTerm} + onChange={(event) => setSearchTerm(event.target.value)} + /> +
+
+
+ + + + + {loading ? ( +
+ +
+ ) : viewMode === 'kanban' ? ( + renderKanban() + ) : ( + renderList() + )} + + +
{translate('::App.Platform.Upload')}
+

+ {translate('::App.Intranet.Document.DisplayNameHint')} +

+
+ {pendingUploads.map((item, index) => ( +
+
+ {item.file.name} · {formatFileSize(item.file.size)} +
+ {item.extension}} + value={item.name} + onChange={(event) => { + const { value } = event.target + setPendingUploads((previous) => + previous.map((entry, entryIndex) => + entryIndex === index ? { ...entry, name: value } : entry, + ), + ) + }} + /> +
+ ))} +
+
+ + +
+
+ + !busy && setDeleteOpen(false)} + onRequestClose={() => !busy && setDeleteOpen(false)} + > +
{translate('::App.Platform.Delete')}
+

{translate('::App.Intranet.Document.DeleteConfirm')}

+
    + {selectedDocuments.map((item) => ( +
  • {item.name}
  • + ))} +
+
+ + +
+
+ + ) +} + +export default IntranetDocuments diff --git a/ui/src/views/intranet/documents/documentUtils.ts b/ui/src/views/intranet/documents/documentUtils.ts new file mode 100644 index 00000000..34540fc2 --- /dev/null +++ b/ui/src/views/intranet/documents/documentUtils.ts @@ -0,0 +1,67 @@ +import type { FileItem } from '@/types/fileManagement' + +/** Intranet dokümanlarının yayında olduğu CDN klasörü; widget yalnızca bu klasörü okur. */ +export const ACTIVE_FOLDER_ID = 'intranet' + +/** Yayından kaldırılan dosyalar alt klasöre taşınır, böylece widget'ta görünmez. */ +export const PASSIVE_FOLDER_ID = 'intranet|_passive' + +export interface IntranetDocument extends FileItem { + isActive: boolean +} + +export interface PendingUpload { + file: File + /** Kullanıcının girdiği okunabilir ad; uzantı hariç tutulur. */ + name: string + /** Nokta ile birlikte uzantı (".pdf"); ad değişse de korunur. */ + extension: string +} + +/** + * Pano kolonlarının rengi uzantıdan türetilir: sabit bir uzantı listesi tutulmaz, + * klasöre hangi tür yüklenirse kolonu o türe göre kendiliğinden oluşur. + */ +const TONES = [ + 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-200', + 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-200', + 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-200', + 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-200', + 'bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-200', + 'bg-cyan-100 text-cyan-700 dark:bg-cyan-900/40 dark:text-cyan-200', + 'bg-pink-100 text-pink-700 dark:bg-pink-900/40 dark:text-pink-200', +] + +/** Uzantıyı noktasız ve küçük harfe indirger; uzantısız dosyalar boş anahtar alır. */ +export const normalizeExtension = (extension?: string): string => + (extension || '').replace('.', '').toLowerCase() + +export const toneOf = (extension?: string): string => { + const key = normalizeExtension(extension) + if (!key) return TONES[TONES.length - 1] + + let hash = 0 + for (let index = 0; index < key.length; index += 1) { + hash = (hash * 31 + key.charCodeAt(index)) % TONES.length + } + return TONES[hash] +} + +export const formatFileSize = (bytes?: number): string => { + const value = bytes ?? 0 + if (value === 0) return '0 B' + + const units = ['B', 'KB', 'MB', 'GB'] + const index = Math.min(Math.floor(Math.log(value) / Math.log(1024)), units.length - 1) + return `${parseFloat((value / Math.pow(1024, index)).toFixed(2))} ${units[index]}` +} + +/** Uzantı ayrı taşınır; kullanıcı yalnızca okunabilir kısmı düzenler. */ +export const splitFileName = (fileName: string): { base: string; extension: string } => { + const dotIndex = fileName.lastIndexOf('.') + return dotIndex > 0 + ? { base: fileName.slice(0, dotIndex), extension: fileName.slice(dotIndex) } + : { base: fileName, extension: '' } +} + +export const baseNameOf = (fileName: string): string => splitFileName(fileName).base diff --git a/ui/src/views/intranet/widgets/RecentDocuments.tsx b/ui/src/views/intranet/widgets/Documents.tsx similarity index 85% rename from ui/src/views/intranet/widgets/RecentDocuments.tsx rename to ui/src/views/intranet/widgets/Documents.tsx index 89b77032..aaaffad4 100644 --- a/ui/src/views/intranet/widgets/RecentDocuments.tsx +++ b/ui/src/views/intranet/widgets/Documents.tsx @@ -7,16 +7,9 @@ import { getFileIcon, getFileType } from '@/proxy/intranet/utils' import { FILE_URL } from '@/constants/app.constant' import Button from '@/components/ui/Button' import type { DashboardWidgetDefinition } from '../dashboardWidget' +import { baseNameOf, formatFileSize } from '../documents/documentUtils' -const formatFileSize = (bytes: number): string => { - if (bytes === 0) return '0 B' - const k = 1024 - const sizes = ['B', 'KB', 'MB', 'GB'] - const i = Math.floor(Math.log(bytes) / Math.log(k)) - return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}` -} - -const RecentDocuments: React.FC<{ documents: DocumentDto[] }> = ({ documents }) => { +const Documents: React.FC<{ documents: DocumentDto[] }> = ({ documents }) => { const { translate } = useLocalization() return (
@@ -24,7 +17,7 @@ const RecentDocuments: React.FC<{ documents: DocumentDto[] }> = ({ documents })

- {translate('::App.DashboardWidget.Documents')} + {translate('::App.Intranet.Document')}

@@ -40,8 +33,11 @@ const RecentDocuments: React.FC<{ documents: DocumentDto[] }> = ({ documents }) {getFileIcon(doc.extension)}
-

- {doc.name} +

+ {baseNameOf(doc.name)}

{getFileType(doc.extension)} @@ -97,7 +93,7 @@ const RecentDocuments: React.FC<{ documents: DocumentDto[] }> = ({ documents }) export const dashboardWidget: DashboardWidgetDefinition = { id: 'documents', - render: ({ dashboard }) => , + render: ({ dashboard }) => , } -export default RecentDocuments +export default Documents