Skip to content

Commit

Permalink
Merge pull request #512 from Shelf-nu/hunar/dashboard
Browse files Browse the repository at this point in the history
Dashboard
  • Loading branch information
DonKoko authored Nov 14, 2023
2 parents 975c533 + cfadc3c commit 00a494d
Show file tree
Hide file tree
Showing 30 changed files with 2,022 additions and 9 deletions.
27 changes: 27 additions & 0 deletions app/components/dashboard/announcement-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { useLoaderData } from "@remix-run/react";
import type { loader } from "~/routes/_layout+/dashboard";
import { StarsIcon } from "../icons";
import { MarkdownViewer } from "../markdown";
import { Button } from "../shared/button";

export default function AnnouncementBar() {
const { announcement } = useLoaderData<typeof loader>();
return announcement ? (
<div className="mb:gap-4 mb:p-4 mb-4 flex items-center gap-2 rounded border border-gray-200 px-2 py-3">
<div className="inline-flex items-center justify-center rounded-full border-[6px] border-solid border-primary-50 bg-primary-100 p-1.5 text-primary">
<StarsIcon />
</div>
<div className="flex-1 items-center gap-1.5 xl:flex">
<div>
<MarkdownViewer content={announcement.content} />
</div>
</div>
<Button variant="primary" to={announcement.link} className="">
{announcement.linkText}
</Button>
{/* <Button variant="secondary" className="border-none bg-transparent">
<XIcon />
</Button> */}
</div>
) : null;
}
100 changes: 100 additions & 0 deletions app/components/dashboard/assets-by-category-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useLoaderData } from "@remix-run/react";
import type { Color } from "@tremor/react";
import { DonutChart } from "@tremor/react";
import { ClientOnly } from "remix-utils/client-only";
import type { loader } from "~/routes/_layout+/dashboard";
import { EmptyState } from "./empty-state";
import { Badge, Button } from "../shared";
import { InfoTooltip } from "../shared/info-tooltip";

export default function AssetsByCategoryChart() {
const { assetsByCategory } = useLoaderData<typeof loader>();
const chartColors: Color[] = [
"slate",
"sky",
"rose",
"orange",
"red",
"purple",
];

const correspondingChartColorsHex: string[] = [
"#64748b",
"#0ea5e9",
"#f43f5e",
"#f97316",
"#ef4444",
"#a855f7",
];

return (
<ClientOnly fallback={null}>
{() => (
<div className="border border-gray-200">
<div className="flex items-center justify-between">
<div className="flex-1 border-b p-4 text-left text-[14px] font-semibold text-gray-900 md:px-6">
Assets by category (top 6)
</div>
<div className="border-b p-4 text-right text-[14px] font-semibold text-gray-900 md:px-6">
<InfoTooltip
content={
<>
<h6>Assets by Category</h6>
<p>
Below graph shows how many percent of assets are in which
category{" "}
</p>
</>
}
/>
</div>
</div>
<div className="h-full p-8">
{assetsByCategory.length > 0 ? (
<div className="flex flex-col items-center gap-4 lg:flex-row lg:justify-between">
<DonutChart
className="mt-6 h-[240px] w-[240px] 2xl:h-[320px] 2xl:w-[320px]"
data={assetsByCategory}
category="assets"
index="category"
showAnimation={true}
animationDuration={400}
colors={chartColors}
/>
<div className="min-w-[140px]">
<ul className="flex flex-wrap items-center lg:block">
{assetsByCategory.map((cd, i) => (
<li className="my-1" key={cd.category}>
<Button
to={`/assets?category=${cd.id}`}
variant="link"
className="border text-gray-700 hover:text-gray-500"
>
<Badge color={correspondingChartColorsHex[i]} noBg>
<span className="text-gray-600">
<strong className="text-gray-900">
{cd.assets}
</strong>{" "}
{cd.category}
</span>
</Badge>
</Button>
</li>
))}
<li>
<Button to="/categories" variant="link">
See all
</Button>
</li>
</ul>
</div>
</div>
) : (
<EmptyState text="No assets in database" />
)}
</div>
</div>
)}
</ClientOnly>
);
}
81 changes: 81 additions & 0 deletions app/components/dashboard/assets-by-status-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { useLoaderData } from "@remix-run/react";
import { DonutChart } from "@tremor/react";
import { ClientOnly } from "remix-utils/client-only";
import type { loader } from "~/routes/_layout+/dashboard";
import { EmptyState } from "./empty-state";
import { Badge } from "../shared";
import { InfoTooltip } from "../shared/info-tooltip";

export default function AssetsByStatusChart() {
const { assetsByStatus } = useLoaderData<typeof loader>();

const { chartData, availableAssets, inCustodyAssets } = assetsByStatus;

return (
<ClientOnly fallback={null}>
{() => (
<div className="w-full border border-gray-200 ">
<div className="flex items-center justify-between">
<div className="flex-1 border-b p-4 text-left text-[14px] font-semibold text-gray-900 md:px-6">
Assets by status
</div>
<div className="border-b p-4 text-right text-[14px] font-semibold text-gray-900 md:px-6">
<InfoTooltip
content={
<>
<h6>Assets by Status</h6>
<p>
Below graph shows how many percent of assets are in which
status{" "}
</p>
</>
}
/>
</div>
</div>
<div className="h-full p-8">
{chartData?.length > 0 ? (
<div className="flex flex-col items-center gap-4 lg:flex-row lg:justify-between">
<DonutChart
className="mt-6 h-[240px] w-[240px] 2xl:h-[320px] 2xl:w-[320px]"
data={chartData}
category="assets"
index="status"
colors={["green", "blue"]}
showAnimation={true}
animationDuration={400}
/>
<div className="min-w-[140px]">
<ul className="flex flex-wrap items-center lg:block">
<li>
<Badge color="#22c55e" noBg>
<span className="text-gray-600">
<strong className="text-gray-900">
{availableAssets}
</strong>{" "}
Available
</span>
</Badge>
</li>
<li>
<Badge color="#3b82f6" noBg>
<span className="text-gray-600">
<strong className="text-gray-900">
{inCustodyAssets}
</strong>{" "}
In Custody
</span>
</Badge>
</li>
</ul>
</div>
</div>
) : (
<EmptyState text="No assets in database" />
)}
</div>
</div>
)}
</ClientOnly>
);
}
51 changes: 51 additions & 0 deletions app/components/dashboard/assets-for-each-month.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useLoaderData } from "@remix-run/react";
import { AreaChart, Card, Title } from "@tremor/react";
import { ClientOnly } from "remix-utils/client-only";
import type { loader } from "~/routes/_layout+/dashboard";
import { InfoTooltip } from "../shared/info-tooltip";

export default function AssetsForEachMonth() {
const { totalAssetsAtEndOfEachMonth, totalAssets } =
useLoaderData<typeof loader>();
return (
<ClientOnly fallback={null}>
{() => (
<Card className="mb-10 py-4 lg:p-6">
<Title>
<div className="flex justify-between">
<div>
<span className="mb-2 block text-[14px] font-medium">
Total inventory
</span>
<span className="block text-[30px] font-semibold text-gray-900">
{totalAssets} assets
</span>
</div>
<InfoTooltip
content={
<>
<h6>Total inventory</h6>
<p>
Below graph shows the total assets you have created in the
last year
</p>
</>
}
/>
</div>
</Title>
<AreaChart
className="mt-4 h-72"
data={totalAssetsAtEndOfEachMonth}
index="month"
categories={["Total assets"]}
colors={["orange"]}
showAnimation={true}
animationDuration={400}
curveType="monotone"
/>
</Card>
)}
</ClientOnly>
);
}
95 changes: 95 additions & 0 deletions app/components/dashboard/custodians.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { useLoaderData } from "@remix-run/react";
import type { TeamMemberWithUser } from "~/modules/team-member/types";
import type { loader } from "~/routes/_layout+/dashboard";
import { EmptyState } from "./empty-state";
import { InfoTooltip } from "../shared/info-tooltip";
import { Table, Td, Tr } from "../table";

export default function CustodiansList() {
const { custodiansData } = useLoaderData<typeof loader>();

return (
<>
<div className="rounded-t border border-b-0 border-gray-200">
<div className="flex items-center justify-between">
<div className="flex-1 p-4 text-left text-[14px] font-semibold text-gray-900 md:px-6">
Custodians
</div>
<div className=" p-4 text-right text-[14px] font-semibold text-gray-900 md:px-6">
<InfoTooltip
content={
<>
<h6>Custodians</h6>
<p>Below listed custodians hold the most assets</p>
</>
}
/>
</div>
</div>
</div>

{custodiansData.length > 0 ? (
<Table className="h-full rounded border border-gray-200 p-8">
<tbody>
{custodiansData.map((cd) => (
<Tr key={cd.id} className="h-[72px]">
{/**
* @TODO this needs to be resolved. Its because of the createdAt & updatedAt fields.
* We need a global solution for this as it happens everywhere
* @ts-ignore */}
<Row custodian={cd.custodian} count={cd.count} />
</Tr>
))}
{custodiansData.length < 5 &&
Array(5 - custodiansData.length)
.fill(null)
.map((_d, i) => (
<Tr key={i} className="h-[72px]">
{""}
</Tr>
))}
</tbody>
</Table>
) : (
<div className="h-full flex-1 rounded-b border border-gray-200 p-8">
<EmptyState text="No assets in custody" />
</div>
)}
</>
);
}

function Row({
custodian,
count,
}: {
custodian: TeamMemberWithUser;
count: number;
}) {
return (
<>
<Td className="w-full">
<div className="flex items-center justify-between">
<span className="text-text-sm font-medium text-gray-900">
<div className="flex items-center gap-3">
<img
src={
custodian?.user?.profilePicture
? custodian?.user?.profilePicture
: "/images/default_pfp.jpg"
}
className={"h-10 w-10 rounded-[4px]"}
alt={`${custodian.name}'s profile`}
/>
<div>
<span className="mt-[1px]">{custodian.name}</span>
<span className="block text-gray-600">{count} Assets</span>
</div>
</div>
</span>
</div>
</Td>
<Td>{""}</Td>
</>
);
}
12 changes: 12 additions & 0 deletions app/components/dashboard/empty-state.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function EmptyState({ text }: { text: string }) {
return (
<div className="flex h-full min-h-[200px] w-full flex-col items-center justify-center">
<img
src="/images/empty-state.svg"
alt="Empty state"
className="h-auto w-[45px]"
/>
<div className="text-center font-semibold text-gray-900">{text}</div>
</div>
);
}
Loading

0 comments on commit 00a494d

Please sign in to comment.