Skip to content

Commit

Permalink
Merge pull request #83 from design-sparx/feat/82-file-manager-ui
Browse files Browse the repository at this point in the history
Add file manager route and update dependencies
  • Loading branch information
kelvink96 authored Dec 8, 2024
2 parents 5f92deb + 2821176 commit b7c6b5f
Show file tree
Hide file tree
Showing 24 changed files with 1,616 additions and 415 deletions.
5 changes: 5 additions & 0 deletions .changeset/gold-rocks-appear.md
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
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 app/apps/file-manager/components/ActionButton/ActionButton.tsx
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 app/apps/file-manager/components/FileButton/FileButton.module.css
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 app/apps/file-manager/components/FileButton/FileButton.tsx
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>
);
}
111 changes: 111 additions & 0 deletions app/apps/file-manager/components/FilesTable.tsx
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}
/>
);
}
3 changes: 3 additions & 0 deletions app/apps/file-manager/components/index.ts
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';
157 changes: 157 additions & 0 deletions app/apps/file-manager/layout.tsx
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.
Loading

0 comments on commit b7c6b5f

Please sign in to comment.