feat(dashboard-v2): add Files page
This commit is contained in:
parent
46a2ff44d5
commit
fab732c6cc
|
@ -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;
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./FileList";
|
|
@ -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;
|
|
@ -2,8 +2,28 @@ import * as React from "react";
|
||||||
|
|
||||||
import DashboardLayout from "../layouts/DashboardLayout";
|
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 = () => {
|
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;
|
FilesPage.Layout = DashboardLayout;
|
||||||
|
|
Reference in New Issue