101
common/resources/client/admin/file-entry/file-entry-index-filters.ts
Executable file
101
common/resources/client/admin/file-entry/file-entry-index-filters.ts
Executable file
@@ -0,0 +1,101 @@
|
||||
import {
|
||||
BackendFilter,
|
||||
FilterControlType,
|
||||
FilterOperator,
|
||||
FilterSelectControl,
|
||||
} from '../../datatable/filters/backend-filter';
|
||||
import {message} from '../../i18n/message';
|
||||
import {USER_MODEL} from '../../auth/user';
|
||||
import {
|
||||
createdAtFilter,
|
||||
updatedAtFilter,
|
||||
} from '@common/datatable/filters/timestamp-filters';
|
||||
|
||||
export const FILE_ENTRY_TYPE_FILTER: BackendFilter<FilterSelectControl> = {
|
||||
key: 'type',
|
||||
label: message('Type'),
|
||||
description: message('Type of the file'),
|
||||
defaultOperator: FilterOperator.eq,
|
||||
control: {
|
||||
type: FilterControlType.Select,
|
||||
defaultValue: '05',
|
||||
options: [
|
||||
{key: '02', label: message('Text'), value: 'text'},
|
||||
{
|
||||
key: '03',
|
||||
label: message('Audio'),
|
||||
value: 'audio',
|
||||
},
|
||||
{
|
||||
key: '04',
|
||||
label: message('Video'),
|
||||
value: 'video',
|
||||
},
|
||||
{
|
||||
key: '05',
|
||||
label: message('Image'),
|
||||
value: 'image',
|
||||
},
|
||||
{key: '06', label: message('PDF'), value: 'pdf'},
|
||||
{
|
||||
key: '07',
|
||||
label: message('Spreadsheet'),
|
||||
value: 'spreadsheet',
|
||||
},
|
||||
{
|
||||
key: '08',
|
||||
label: message('Word Document'),
|
||||
value: 'word',
|
||||
},
|
||||
{
|
||||
key: '09',
|
||||
label: message('Photoshop'),
|
||||
value: 'photoshop',
|
||||
},
|
||||
{
|
||||
key: '10',
|
||||
label: message('Archive'),
|
||||
value: 'archive',
|
||||
},
|
||||
{
|
||||
key: '11',
|
||||
label: message('Folder'),
|
||||
value: 'folder',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const FILE_ENTRY_INDEX_FILTERS: BackendFilter[] = [
|
||||
FILE_ENTRY_TYPE_FILTER,
|
||||
{
|
||||
key: 'public',
|
||||
label: message('Visibility'),
|
||||
description: message('Whether file is publicly accessible'),
|
||||
defaultOperator: FilterOperator.eq,
|
||||
control: {
|
||||
type: FilterControlType.Select,
|
||||
defaultValue: '01',
|
||||
options: [
|
||||
{key: '01', label: message('Private'), value: false},
|
||||
{key: '02', label: message('Public'), value: true},
|
||||
],
|
||||
},
|
||||
},
|
||||
createdAtFilter({
|
||||
description: message('Date file was uploaded'),
|
||||
}),
|
||||
updatedAtFilter({
|
||||
description: message('Date file was last changed'),
|
||||
}),
|
||||
{
|
||||
key: 'owner_id',
|
||||
label: message('Uploader'),
|
||||
description: message('User that this file was uploaded by'),
|
||||
defaultOperator: FilterOperator.eq,
|
||||
control: {
|
||||
type: FilterControlType.SelectModel,
|
||||
model: USER_MODEL,
|
||||
},
|
||||
},
|
||||
];
|
||||
130
common/resources/client/admin/file-entry/file-entry-index-page.tsx
Executable file
130
common/resources/client/admin/file-entry/file-entry-index-page.tsx
Executable file
@@ -0,0 +1,130 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import {DataTablePage} from '../../datatable/page/data-table-page';
|
||||
import {IconButton} from '../../ui/buttons/icon-button';
|
||||
import {FormattedDate} from '../../i18n/formatted-date';
|
||||
import {ColumnConfig} from '../../datatable/column-config';
|
||||
import {Trans} from '../../i18n/trans';
|
||||
import {DeleteSelectedItemsAction} from '../../datatable/page/delete-selected-items-action';
|
||||
import {DataTableEmptyStateMessage} from '../../datatable/page/data-table-emty-state-message';
|
||||
import {DialogTrigger} from '../../ui/overlays/dialog/dialog-trigger';
|
||||
import {FileEntry} from '../../uploads/file-entry';
|
||||
import {NameWithAvatar} from '../../datatable/column-templates/name-with-avatar';
|
||||
import {User} from '../../auth/user';
|
||||
import {CheckIcon} from '../../icons/material/Check';
|
||||
import {CloseIcon} from '../../icons/material/Close';
|
||||
import {FormattedBytes} from '../../uploads/formatted-bytes';
|
||||
import {VisibilityIcon} from '../../icons/material/Visibility';
|
||||
import uploadSvg from './upload.svg';
|
||||
import {FilePreviewDialog} from '../../uploads/preview/file-preview-dialog';
|
||||
import {FILE_ENTRY_INDEX_FILTERS} from './file-entry-index-filters';
|
||||
import {FileTypeIcon} from '../../uploads/file-type-icon/file-type-icon';
|
||||
|
||||
const columnConfig: ColumnConfig<FileEntry>[] = [
|
||||
{
|
||||
key: 'name',
|
||||
allowsSorting: true,
|
||||
visibleInMode: 'all',
|
||||
width: 'flex-3 min-w-200',
|
||||
header: () => <Trans message="Name" />,
|
||||
body: entry => (
|
||||
<Fragment>
|
||||
<div className="overflow-x-hidden overflow-ellipsis">{entry.name}</div>
|
||||
<div className="text-muted text-xs overflow-x-hidden overflow-ellipsis">
|
||||
{entry.file_name}
|
||||
</div>
|
||||
</Fragment>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'owner_id',
|
||||
allowsSorting: true,
|
||||
width: 'flex-3 min-w-200',
|
||||
header: () => <Trans message="Uploader" />,
|
||||
body: entry => {
|
||||
const user = entry.users?.[0] as User;
|
||||
if (!user) return null;
|
||||
return (
|
||||
<NameWithAvatar
|
||||
image={user.avatar}
|
||||
label={user.display_name}
|
||||
description={user.email}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
width: 'w-100 flex-shrink-0',
|
||||
allowsSorting: true,
|
||||
header: () => <Trans message="Type" />,
|
||||
body: entry => (
|
||||
<div className="flex items-center gap-12">
|
||||
<FileTypeIcon type={entry.type} className="w-24 h-24 overflow-hidden" />
|
||||
<div className="capitalize">{entry.type}</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'public',
|
||||
allowsSorting: true,
|
||||
width: 'w-60 flex-shrink-0',
|
||||
header: () => <Trans message="Public" />,
|
||||
body: entry =>
|
||||
entry.public ? (
|
||||
<CheckIcon className="icon-md text-positive" />
|
||||
) : (
|
||||
<CloseIcon className="icon-md text-danger" />
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'file_size',
|
||||
allowsSorting: true,
|
||||
maxWidth: 'max-w-100',
|
||||
header: () => <Trans message="File size" />,
|
||||
body: entry => <FormattedBytes bytes={entry.file_size} />,
|
||||
},
|
||||
{
|
||||
key: 'updated_at',
|
||||
allowsSorting: true,
|
||||
width: 'w-100',
|
||||
header: () => <Trans message="Last updated" />,
|
||||
body: entry => <FormattedDate date={entry.updated_at} />,
|
||||
},
|
||||
{
|
||||
key: 'actions',
|
||||
header: () => <Trans message="Actions" />,
|
||||
hideHeader: true,
|
||||
align: 'end',
|
||||
width: 'w-42 flex-shrink-0',
|
||||
visibleInMode: 'all',
|
||||
body: entry => {
|
||||
return (
|
||||
<DialogTrigger type="modal">
|
||||
<IconButton size="md" className="text-muted">
|
||||
<VisibilityIcon />
|
||||
</IconButton>
|
||||
<FilePreviewDialog entries={[entry]} />
|
||||
</DialogTrigger>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function FileEntryIndexPage() {
|
||||
return (
|
||||
<DataTablePage
|
||||
endpoint="file-entries"
|
||||
title={<Trans message="Uploaded files and folders" />}
|
||||
columns={columnConfig}
|
||||
filters={FILE_ENTRY_INDEX_FILTERS}
|
||||
selectedActions={<DeleteSelectedItemsAction />}
|
||||
emptyStateMessage={
|
||||
<DataTableEmptyStateMessage
|
||||
image={uploadSvg}
|
||||
title={<Trans message="Nothing has been uploaded yet" />}
|
||||
filteringTitle={<Trans message="No matching files or folders" />}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
1
common/resources/client/admin/file-entry/upload.svg
Executable file
1
common/resources/client/admin/file-entry/upload.svg
Executable file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
Reference in New Issue
Block a user