-
Notifications
You must be signed in to change notification settings - Fork 61
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #83 from design-sparx/feat/82-file-manager-ui
Add file manager route and update dependencies
- Loading branch information
Showing
24 changed files
with
1,616 additions
and
415 deletions.
There are no files selected for viewing
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,5 @@ | ||
--- | ||
"mantine-analytics-dashboard": patch | ||
--- | ||
|
||
feat: added file manager route and update dependencies |
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.