feat(dashboard-v2): add Files page

This commit is contained in:
Michał Leszczyk 2022-03-13 10:57:20 +01:00
parent 46a2ff44d5
commit fab732c6cc
No known key found for this signature in database
GPG Key ID: FA123CA8BAA2FBF4
5 changed files with 233 additions and 1 deletions

View File

@ -0,0 +1,74 @@
import * as React from "react";
import useSWR from "swr";
import { useMedia } from "react-use";
import theme from "../../lib/theme";
import { ContainerLoadingIndicator } from "../LoadingIndicator";
import { Select, SelectOption } from "../Select";
import { Switch } from "../Switch";
import { TextInputIcon } from "../TextInputIcon/TextInputIcon";
import { SearchIcon } from "../Icons";
import FileTable from "./FileTable";
import useFormattedFilesData from "./useFormattedFilesData";
const FileList = ({ type }) => {
const isMediumScreenOrLarger = useMedia(`(min-width: ${theme.screens.md})`);
const { data, error } = useSWR(`user/${type}?pageSize=10`);
const items = useFormattedFilesData(data?.items || []);
const setFilter = (name, value) => console.log("filter", name, "set to", value);
if (!items.length) {
return (
<div className="flex w-full h-full justify-center items-center text-palette-400">
{/* TODO: proper error message */}
{!data && !error && <ContainerLoadingIndicator />}
{!data && error && <p>An error occurred while loading this data.</p>}
{data && <p>No {type} found.</p>}
</div>
);
}
return (
<div className="flex flex-col gap-4 pt-4">
<div className="flex flex-col gap-4 lg:flex-row justify-between items-center">
<TextInputIcon
className="w-full lg:w-[280px] xl:w-[420px]"
placeholder="Search"
icon={<SearchIcon size={20} />}
onChange={console.log.bind(console)}
/>
<div className="flex flex-row items-center uppercase font-light text-sm gap-4">
<Switch onChange={(value) => setFilter("showSmallFiles", value)} className="mr-8">
<span className="underline decoration-dashed underline-offset-2 decoration-2 decoration-gray-300">
Show small files
</span>
</Switch>
<div>
<span className="pr-2">File type:</span>
<Select onChange={(value) => setFilter("type", value)}>
<SelectOption value="all" label="All" />
<SelectOption value="mp4" label=".mp4" />
<SelectOption value="pdf" label=".pdf" />
</Select>
</div>
<div>
<span className="pr-2">Sort:</span>
<Select onChange={(value) => setFilter("type", value)}>
<SelectOption value="size-desc" label="Biggest size" />
<SelectOption value="size-asc" label="Smallest size" />
<SelectOption value="date-desc" label="Latest" />
<SelectOption value="date-asc" label="Oldest" />
</Select>
</div>
</div>
</div>
{/* TODO: mobile view (it's not tabular) */}
{isMediumScreenOrLarger ? <FileTable items={items} /> : "Mobile view"}
</div>
);
};
export default FileList;

View File

@ -0,0 +1,111 @@
import { CogIcon, ShareIcon } from "../Icons";
import { PopoverMenu } from "../PopoverMenu/PopoverMenu";
import { Table, TableBody, TableCell, TableHead, TableHeadCell, TableRow } from "../Table";
import { CopyButton } from "../CopyButton";
const buildShareMenu = (item) => {
return [
{
label: "Facebook",
callback: () => {
console.info("share to Facebook", item);
},
},
{
label: "Twitter",
callback: () => {
console.info("share to Twitter", item);
},
},
{
label: "Discord",
callback: () => {
console.info("share to Discord", item);
},
},
];
};
const buildOptionsMenu = (item) => {
return [
{
label: "Preview",
callback: () => {
console.info("preview", item);
},
},
{
label: "Download",
callback: () => {
console.info("download", item);
},
},
{
label: "Unpin",
callback: () => {
console.info("unpin", item);
},
},
{
label: "Report",
callback: () => {
console.info("report", item);
},
},
];
};
export default function FileTable({ items }) {
return (
<Table style={{ tableLayout: "fixed" }}>
<TableHead>
<TableRow noHoverEffect>
<TableHeadCell className="w-[240px] xl:w-[360px]">Name</TableHeadCell>
<TableHeadCell className="w-[80px]">Type</TableHeadCell>
<TableHeadCell className="w-[80px]" align="right">
Size
</TableHeadCell>
<TableHeadCell className="w-[180px]">Uploaded</TableHeadCell>
<TableHeadCell className="hidden lg:table-cell">Skylink</TableHeadCell>
<TableHeadCell className="w-[100px]">Activity</TableHeadCell>
</TableRow>
</TableHead>
<TableBody>
{items.map((item) => {
const { id, name, type, size, date, skylink } = item;
return (
<TableRow key={id}>
<TableCell className="w-[240px] xl:w-[360px]">{name}</TableCell>
<TableCell className="w-[80px]">{type}</TableCell>
<TableCell className="w-[80px]" align="right">
{size}
</TableCell>
<TableCell className="w-[180px]">{date}</TableCell>
<TableCell className="hidden lg:table-cell pr-6 !overflow-visible">
<div className="flex items-center">
<CopyButton value={skylink} className="mr-2" />
<span className="w-full inline-block truncate">{skylink}</span>
</div>
</TableCell>
<TableCell className="w-[100px] !overflow-visible">
<div className="flex text-palette-600 gap-4">
<PopoverMenu options={buildShareMenu(item)} openClassName="text-primary">
<button>
<ShareIcon size={22} />
</button>
</PopoverMenu>
<PopoverMenu options={buildOptionsMenu(item)} openClassName="text-primary">
<button>
<CogIcon />
</button>
</PopoverMenu>
</div>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
);
}

View File

@ -0,0 +1 @@
export * from "./FileList";

View File

@ -0,0 +1,26 @@
import { useMemo } from "react";
import prettyBytes from "pretty-bytes";
import dayjs from "dayjs";
const parseFileName = (fileName) => {
const lastDotIndex = Math.max(0, fileName.lastIndexOf(".")) || Infinity;
return [fileName.substr(0, lastDotIndex), fileName.substr(lastDotIndex)];
};
const formatItem = ({ size, name: rawFileName, uploadedOn, downloadedOn, ...rest }) => {
const [name, type] = parseFileName(rawFileName);
const date = dayjs(uploadedOn || downloadedOn).format("MM/DD/YYYY; HH:MM");
return {
...rest,
date,
size: prettyBytes(size),
type,
name,
};
};
const useFormattedFilesData = (items) => useMemo(() => items.map(formatItem), [items]);
export default useFormattedFilesData;

View File

@ -2,8 +2,28 @@ import * as React from "react";
import DashboardLayout from "../layouts/DashboardLayout";
import { Panel } from "../components/Panel";
import { Tab, TabPanel, Tabs } from "../components/Tabs";
import FileList from "../components/FileList/FileList";
import { useSearchParam } from "react-use";
const FilesPage = () => {
return <>FILES</>;
const defaultTab = useSearchParam("tab");
return (
<Panel title="Files">
<Tabs defaultTab={defaultTab || "uploads"}>
<Tab id="uploads" title="Uploads" />
<Tab id="downloads" title="Downloads" />
<TabPanel tabId="uploads" className="pt-4">
<FileList type="uploads" />
</TabPanel>
<TabPanel tabId="downloads" className="pt-4">
<FileList type="downloads" />
</TabPanel>
</Tabs>
</Panel>
);
};
FilesPage.Layout = DashboardLayout;