-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: added file manager support and update dependencies
Introduced a new file manager route and associated types to handle file operations effectively. Added mock data for file activities and folders to simulate the file management functionality. Updated the Mantine and Tiptap dependencies to their latest versions for improved performance and new features.
- Loading branch information
Showing
23 changed files
with
1,611 additions
and
415 deletions.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
app/apps/file-manager/components/ActionButton/ActionButton.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
.wrapper { | ||
padding: var(--mantine-spacing-md); | ||
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-7)); | ||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); | ||
border-radius: var(--mantine-radius-default); | ||
box-shadow: var(--mantine-shadow-md); | ||
|
||
&[data-primary="true"] { | ||
background-color: var(--mantine-primary-color-filled); | ||
border-color: var(--mantine-primary-color-filled); | ||
color: var(--mantine-color-white); | ||
} | ||
|
||
@mixin hover { | ||
transition: all ease 150ms; | ||
border-color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-2)); | ||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6)); | ||
|
||
&[data-primary="true"] { | ||
background-color: var(--mantine-primary-color-filled-hover); | ||
} | ||
} | ||
} | ||
|
||
.label { | ||
font-weight: 500; | ||
} |
31 changes: 31 additions & 0 deletions
31
app/apps/file-manager/components/ActionButton/ActionButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { FC } from 'react'; | ||
|
||
import { Text, UnstyledButton, UnstyledButtonProps } from '@mantine/core'; | ||
|
||
import classes from './ActionButton.module.css'; | ||
|
||
type ActionButtonProps = UnstyledButtonProps & { | ||
icon: FC<any>; | ||
label: string; | ||
asPrimary?: boolean; | ||
}; | ||
|
||
export function ActionButton({ | ||
icon: Icon, | ||
label, | ||
asPrimary = false, | ||
...others | ||
}: ActionButtonProps) { | ||
return ( | ||
<UnstyledButton | ||
{...others} | ||
className={classes.wrapper} | ||
data-primary={asPrimary} | ||
> | ||
<Icon size={24} /> | ||
<Text mt={4} className={classes.label}> | ||
{label} | ||
</Text> | ||
</UnstyledButton> | ||
); | ||
} |
14 changes: 14 additions & 0 deletions
14
app/apps/file-manager/components/FileButton/FileButton.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
.wrapper { | ||
display: flex; | ||
align-items: center; | ||
gap: var(--mantine-spacing-sm); | ||
border: 1px solid light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); | ||
border-radius: var(--mantine-radius-default); | ||
padding: var(--mantine-spacing-sm); | ||
|
||
@mixin hover { | ||
transition: all ease 150ms; | ||
border-color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3)); | ||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-6)); | ||
} | ||
} |
41 changes: 41 additions & 0 deletions
41
app/apps/file-manager/components/FileButton/FileButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { | ||
Flex, | ||
Stack, | ||
Text, | ||
UnstyledButton, | ||
UnstyledButtonProps, | ||
} from '@mantine/core'; | ||
import { IconPointFilled } from '@tabler/icons-react'; | ||
|
||
import { IFile } from '@/app/apps/file-manager/types'; | ||
|
||
import classes from './FileButton.module.css'; | ||
import { resolveFileIcon } from '../../utils'; | ||
|
||
type FileButtonProps = UnstyledButtonProps & { | ||
file: IFile; | ||
}; | ||
|
||
export function FileButton({ file, ...others }: FileButtonProps) { | ||
const Icon = resolveFileIcon(file.type); | ||
|
||
return ( | ||
<UnstyledButton className={classes.wrapper} {...others}> | ||
<Icon size={18} /> | ||
<Stack gap={2}> | ||
<Text fz="sm" fw={700}> | ||
{file.name} | ||
</Text> | ||
<Flex gap={4} align="center"> | ||
<Text fz="xs" tt="uppercase"> | ||
{file.size} | ||
</Text> | ||
<IconPointFilled color="gray" size={10} /> | ||
<Text fz="xs" tt="lowercase"> | ||
{file.type} | ||
</Text> | ||
</Flex> | ||
</Stack> | ||
</UnstyledButton> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
'use client'; | ||
|
||
import React, { ReactNode, useEffect, useState } from 'react'; | ||
|
||
import { Flex, Text, ThemeIcon } from '@mantine/core'; | ||
import { useDebouncedValue } from '@mantine/hooks'; | ||
import sortBy from 'lodash/sortBy'; | ||
import { | ||
DataTable, | ||
DataTableProps, | ||
DataTableSortStatus, | ||
} from 'mantine-datatable'; | ||
|
||
import { IFile } from '@/app/apps/file-manager/types'; | ||
import { resolveFileIcon } from '@/app/apps/file-manager/utils'; | ||
import { ErrorAlert } from '@/components'; | ||
|
||
const PAGE_SIZES = [10, 15, 20]; | ||
|
||
type FilesTableProps = { | ||
data: IFile[]; | ||
error?: ReactNode; | ||
loading?: boolean; | ||
}; | ||
|
||
export function FilesTable({ data, loading, error }: FilesTableProps) { | ||
const [page, setPage] = useState(1); | ||
const [pageSize, setPageSize] = useState(PAGE_SIZES[2]); | ||
const [selectedRecords, setSelectedRecords] = useState<IFile[]>([]); | ||
const [records, setRecords] = useState<IFile[]>(data.slice(0, pageSize)); | ||
const [sortStatus, setSortStatus] = useState<DataTableSortStatus>({ | ||
columnAccessor: 'product', | ||
direction: 'asc', | ||
}); | ||
const [query, setQuery] = useState(''); | ||
const [debouncedQuery] = useDebouncedValue(query, 200); | ||
const [selectedStatuses, setSelectedStatuses] = useState<string[]>([]); | ||
|
||
const columns: DataTableProps<IFile>['columns'] = [ | ||
{ | ||
accessor: 'name', | ||
render: (item: IFile) => { | ||
const Icon = resolveFileIcon(item.type); | ||
|
||
return ( | ||
<Flex gap="xs" align="center"> | ||
<ThemeIcon variant="default"> | ||
<Icon size={16} /> | ||
</ThemeIcon> | ||
<Text fz="sm">{item.name}</Text> | ||
</Flex> | ||
); | ||
}, | ||
}, | ||
{ | ||
accessor: 'type', | ||
}, | ||
{ | ||
accessor: 'size', | ||
}, | ||
{ | ||
accessor: 'modified_at', | ||
}, | ||
{ | ||
accessor: 'owner', | ||
}, | ||
]; | ||
|
||
useEffect(() => { | ||
setPage(1); | ||
}, [pageSize]); | ||
|
||
useEffect(() => { | ||
const from = (page - 1) * pageSize; | ||
const to = from + pageSize; | ||
const d = sortBy(data, sortStatus.columnAccessor) as IFile[]; | ||
const dd = d.slice(from, to) as IFile[]; | ||
let filtered = sortStatus.direction === 'desc' ? dd.reverse() : dd; | ||
|
||
setRecords(filtered); | ||
}, [sortStatus, data, page, pageSize, debouncedQuery, selectedStatuses]); | ||
|
||
return error ? ( | ||
<ErrorAlert title="Error loading orders" message={error.toString()} /> | ||
) : ( | ||
<DataTable | ||
minHeight={200} | ||
verticalSpacing="sm" | ||
striped={true} | ||
// @ts-ignore | ||
columns={columns} | ||
records={records} | ||
selectedRecords={selectedRecords} | ||
// @ts-ignore | ||
onSelectedRecordsChange={setSelectedRecords} | ||
totalRecords={ | ||
debouncedQuery || selectedStatuses.length > 0 | ||
? records.length | ||
: data.length | ||
} | ||
recordsPerPage={pageSize} | ||
page={page} | ||
onPageChange={(p) => setPage(p)} | ||
recordsPerPageOptions={PAGE_SIZES} | ||
onRecordsPerPageChange={setPageSize} | ||
sortStatus={sortStatus} | ||
onSortStatusChange={setSortStatus} | ||
fetching={loading} | ||
/> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './ActionButton/ActionButton'; | ||
export * from './FileButton/FileButton'; | ||
export * from './FilesTable'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
'use client'; | ||
|
||
import { ReactNode } from 'react'; | ||
|
||
import { DonutChart } from '@mantine/charts'; | ||
import { | ||
Button, | ||
Container, | ||
Flex, | ||
Grid, | ||
Paper, | ||
PaperProps, | ||
Stack, | ||
Text, | ||
ThemeIcon, | ||
Timeline, | ||
Title, | ||
UnstyledButton, | ||
} from '@mantine/core'; | ||
import { IconChevronRight, IconPointFilled } from '@tabler/icons-react'; | ||
import Link from 'next/link'; | ||
|
||
import { IFileActivity, IFolder } from '@/app/apps/file-manager/types'; | ||
import { | ||
resolveActionIcon, | ||
resolveFileIcon, | ||
resolveFolderIcon, | ||
} from '@/app/apps/file-manager/utils'; | ||
import { useFetchData } from '@/hooks'; | ||
|
||
const PAPER_PROPS: PaperProps = { | ||
shadow: 'md', | ||
radius: 'md', | ||
p: 'md', | ||
mb: 'md', | ||
}; | ||
|
||
export default function FileManagerLayout({ | ||
children, | ||
}: { | ||
children: ReactNode; | ||
}) { | ||
const { | ||
data: foldersData, | ||
loading: foldersLoading, | ||
error: foldersError, | ||
} = useFetchData('/mocks/Folders.json'); | ||
const { | ||
data: fileActivityData, | ||
loading: fileActivityLoading, | ||
error: fileActivityError, | ||
} = useFetchData('/mocks/FileActivities.json'); | ||
|
||
const folders = foldersData.slice(0, 5).map((folder: IFolder) => { | ||
const Icon = resolveFolderIcon(folder.name); | ||
|
||
return ( | ||
<Flex key={folder.name} component={UnstyledButton} gap="sm"> | ||
<ThemeIcon variant="default"> | ||
<Icon size={16} /> | ||
</ThemeIcon> | ||
<Stack gap={2}> | ||
<Text fz="sm" fw={600}> | ||
{folder.name} | ||
</Text> | ||
<Flex gap={2} align="center"> | ||
<Text fz="xs">{folder.total_files} files</Text> | ||
<IconPointFilled color="gray" size={12} /> | ||
<Text fz="xs">{folder.estimated_size}</Text> | ||
</Flex> | ||
</Stack> | ||
</Flex> | ||
); | ||
}); | ||
|
||
const fileActivityItems = fileActivityData.map( | ||
(fileActivity: IFileActivity) => { | ||
const ActionIcon = resolveActionIcon(fileActivity.action); | ||
const FileIcon = resolveFileIcon(fileActivity.file_type); | ||
|
||
return ( | ||
<Timeline.Item | ||
key={fileActivity.id} | ||
bullet={<ActionIcon size={16} />} | ||
lineVariant="dashed" | ||
title={ | ||
<Flex gap={2}> | ||
<Text fz="sm">{fileActivity.user}</Text> | ||
<Text fz="sm" tt="lowercase"> | ||
{fileActivity.action} {fileActivity.file_type} | ||
</Text> | ||
</Flex> | ||
} | ||
> | ||
<Flex gap={2} direction="column"> | ||
<Flex align="center" gap={2} component={Link} href="#"> | ||
<FileIcon size={16} /> | ||
<Text fz="sm">{fileActivity.file_name}</Text> | ||
</Flex> | ||
<Text fz="xs">{fileActivity.timestamp}</Text> | ||
</Flex> | ||
</Timeline.Item> | ||
); | ||
}, | ||
); | ||
|
||
return ( | ||
<> | ||
<> | ||
<title>File Manager | DesignSparx</title> | ||
</> | ||
<Container fluid> | ||
<Grid> | ||
<Grid.Col span={{ base: 12, md: 6, lg: 8, xl: 9 }}> | ||
{children} | ||
</Grid.Col> | ||
<Grid.Col span={{ base: 12, md: 6, lg: 4, xl: 3 }}> | ||
<Paper {...PAPER_PROPS}> | ||
<Title order={4} mb="md"> | ||
Storage usage | ||
</Title> | ||
<DonutChart | ||
paddingAngle={8} | ||
data={[ | ||
{ | ||
name: 'used', | ||
value: 70, | ||
color: 'var(--mantine-primary-color-filled)', | ||
}, | ||
{ name: 'free', value: 30, color: 'gray.3' }, | ||
]} | ||
h={220} | ||
mx="auto" | ||
chartLabel="125GB free" | ||
/> | ||
<Stack gap="xs">{folders}</Stack> | ||
</Paper> | ||
<Paper {...PAPER_PROPS}> | ||
<Flex align="center" justify="space-between" mb="md"> | ||
<Title order={4}>Activity</Title> | ||
<Button | ||
variant="subtle" | ||
rightSection={<IconChevronRight size={16} />} | ||
> | ||
See All | ||
</Button> | ||
</Flex> | ||
<Timeline bulletSize={30} lineWidth={2}> | ||
{fileActivityItems} | ||
</Timeline> | ||
</Paper> | ||
</Grid.Col> | ||
</Grid> | ||
</Container> | ||
</> | ||
); | ||
} |
Empty file.
Oops, something went wrong.