Skip to content

Commit

Permalink
feat: added file manager support and update dependencies
Browse files Browse the repository at this point in the history
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
kelvink96 committed Dec 8, 2024
1 parent 5f92deb commit 6dc9de2
Show file tree
Hide file tree
Showing 23 changed files with 1,611 additions and 415 deletions.
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 6dc9de2

Please sign in to comment.