Skip to content

Commit

Permalink
Added desktop view
Browse files Browse the repository at this point in the history
  • Loading branch information
thedanielforum committed Jul 1, 2022
1 parent fb5e891 commit 8c71674
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 5 deletions.
Binary file added assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
127 changes: 127 additions & 0 deletions components/CategoryChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from "react";
import { useIntl } from "react-intl";
import { useQuery, gql } from "@apollo/client";
import { PieChart, Pie, LabelList } from "recharts";
import Dinero from "dinero.js";
import Loading from "components/Loading";

const GET_CATEGORY_CHART_DATA = gql`
query categoryChartData {
queryTransaction {
id
amount
category {
id
icon
name
}
}
}
`;

const CategoryChart: React.FC = () => {
const intl = useIntl();
const {
data: pieChartData,
loading,
error,
} = useQuery(GET_CATEGORY_CHART_DATA, {});
if (error) {
console.error(error);
}

const formatChartData = () => {
let data: {
name: string;
icon: string;
value: number;
categoryId: string;
}[] = [];

if (!pieChartData || !pieChartData.queryTransaction) {
return data;
}

pieChartData.queryTransaction.forEach((item: any) => {
const categoryIndex = data.findIndex(
(c) => c.categoryId === item.category.id
);
if (item.amount < 0) {
if (categoryIndex !== -1) {
data[categoryIndex].value += Math.abs(item.amount);
} else {
data.push({
icon: item.category.icon,
name: item.category.name,
value: Math.abs(item.amount),
categoryId: item.category.id,
});
}
}
});

return data;
};

if (loading) {
return <Loading />;
}

return (
<div>
<PieChart width={300} height={300}>
<Pie
data={formatChartData()}
dataKey="value"
nameKey="icon"
cx="50%"
cy="50%"
innerRadius={50}
outerRadius={80}
fill="#82ca9d"
// label
>
<LabelList
dataKey="icon"
position="outside"
offset={5}
color="#000"
fill="#000"
fontSize={23}
fontWeight="bold"
// formatter={(value: any) => {
// console.log("value", value);
// return value;
// }}
/>
</Pie>
</PieChart>
<div>
<h2 className="text-lg font-semibold">
{intl.formatMessage({ defaultMessage: "Spend by category" })}
</h2>
{formatChartData().map((item: any) => (
<div
key={`row-${item.categoryId}`}
className="flex flex-row justify-between items-center py-2"
>
<div className="flex flex-row">
<div className="flex flex-row justify-center items-center w-12 h-12 bg-blue-100 rounded-lg">
<span className="text-3xl">{item.icon}</span>
</div>
<div className="flex flex-col justify-center ml-2">
<h1 className="font-semibold">{item.name}</h1>
</div>
</div>
<h1 className="text-lg font-semibold text-red-500">
-&nbsp;
{Dinero({ amount: item.value, precision: 2 }).toFormat("$0,0.00")}
</h1>
</div>
))}
</div>
</div>
);
};

export default CategoryChart;
99 changes: 99 additions & 0 deletions components/HeaderNavbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React, { useState } from "react";
import Image from "next/image";
import { useIntl } from "react-intl";
import { useSession } from "next-auth/react";
import toast from "react-hot-toast";
import { gql, useMutation } from "@apollo/client";
import { parseCurrency } from "utils/currency";
import { Dialog } from "@headlessui/react";
import TransactionForm, { TransactionFormValues } from "forms/TransactionForm";
import {
GET_TRANSACTIONS,
TRANSACTION_AMOUNT_AGGREGATE,
} from "constants/queries";
import logo from "assets/images/logo.png";

const ADD_TRANSACTION = gql`
mutation AddTransaction($input: [AddTransactionInput!]!) {
addTransaction(input: $input) {
transaction {
id
}
}
}
`;

const HeaderNavbar: React.FC = () => {
const intl = useIntl();
const { data: session } = useSession();
const [addTransaction] = useMutation(ADD_TRANSACTION, {});

const [open, setOpen] = useState<boolean>(false);

const onNewTransaction = async (values: TransactionFormValues) => {
try {
let amount = values.type === "income" ? values.amount : -values.amount;

await addTransaction({
variables: {
input: [
{
amount: parseCurrency(amount),
date: values.date,
category: { id: values.category },
user: {
// @ts-ignore
id: session.user?.id,
},
},
],
},
refetchQueries: [GET_TRANSACTIONS, TRANSACTION_AMOUNT_AGGREGATE],
});
toast.success(
intl.formatMessage({ defaultMessage: "Transaction added" })
);
setOpen(false);
} catch (err: any) {
console.error(err);
toast.error(err.message);
}
};

return (
<header className="sticky top-0 h-12 border-b-2 border-gray-500 w-full mb-2">
<div className="max-w-screen-md ml-auto mr-auto">
<div className="flex justify-between pt-2">
<button
type="button"
className="rounded-full bg-gradient-to-br from-ikura-light to-ikura-dark text-white pr-2 pl-2"
onClick={() => setOpen(true)}
>
Add transaction
</button>
<Image src={logo} alt="logo" width={30} height={30} />
<Image
src={session?.user?.image || "https://via.placeholder.com/150"}
alt="profile"
className="rounded-full"
width={30}
height={30}
/>
</div>
</div>
<Dialog open={open} onClose={() => setOpen(false)}>
<Dialog.Overlay className="z-40 fixed inset-0 bg-black opacity-30" />
<div className="mr-auto ml-auto" style={{ width: "55vh" }}>
<div
className="z-50 w-full absolute top-0 m-4 p-4 bg-white rounded-md shadow-xl"
style={{ width: 400 }}
>
<TransactionForm onSubmit={onNewTransaction} initialValues={{}} />
</div>
</div>
</Dialog>
</header>
);
};

export default HeaderNavbar;
7 changes: 5 additions & 2 deletions components/MobileNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const MobileNavbar: React.FC = () => {

return (
<Fragment>
<div className="w-full h-12 fixed bottom-0 shadow-reversed bg-white">
<div className="w-full h-12 fixed bottom-0 shadow-reversed bg-white lg:hidden">
<div className="h-full flex flex-row items-center justify-between px-6">
<Link href="/">
<a
Expand Down Expand Up @@ -136,7 +136,10 @@ const MobileNavbar: React.FC = () => {
</div>
<Dialog open={open} onClose={() => setOpen(false)}>
<Dialog.Overlay className="z-40 fixed inset-0 bg-black opacity-30" />
<div className="flex flex-col items-start z-50 w-11/12 absolute top-0 m-4 p-4 bg-white rounded-md shadow-xl">
<div
className="flex flex-col items-start z-50 w-11/12 absolute top-0 m-4 p-4 bg-white rounded-md shadow-xl"
style={{ maxWidth: "400" }}
>
<TransactionForm onSubmit={onNewTransaction} initialValues={{}} />
</div>
</Dialog>
Expand Down
2 changes: 2 additions & 0 deletions components/Protected.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useIntl } from "react-intl";
import toast from "react-hot-toast";
import { UserContext } from "contexts/User";
import Loading from "components/Loading";
import HeaderNavbar from "components/HeaderNavbar";
import MobileNavbar from "components/MobileNavbar";

const Protected: React.FC = (props) => {
Expand All @@ -46,6 +47,7 @@ const Protected: React.FC = (props) => {
return (
<UserContext.Provider value={null}>
<div className="h-screen md:h-full">
{session?.user && <HeaderNavbar />}
<div className="pb-12">{props.children}</div>
{session?.user && <MobileNavbar />}
</div>
Expand Down
2 changes: 1 addition & 1 deletion next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const moduleExports = {
localeDetection: true,
},
images: {
domains: [],
domains: ["lh3.googleusercontent.com"],
},
webpack(config, { dev, ...other }) {
if (!dev) {
Expand Down
5 changes: 4 additions & 1 deletion pages/account/categories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,10 @@ const Categories: NextPage = () => {
</div>
<Dialog open={open} onClose={() => setOpen(false)}>
<Dialog.Overlay className="z-40 fixed inset-0 bg-black opacity-30" />
<div className="flex flex-col items-start z-50 w-11/12 absolute top-0 m-4 p-4 bg-white rounded-md shadow-xl">
<div
className="flex flex-col items-start z-50 w-11/12 absolute top-0 m-4 p-4 bg-white rounded-md shadow-xl"
style={{ maxWidth: "400" }}
>
<CategoryForm onSubmit={onCategorySubmit} initialValues={{}} />
</div>
</Dialog>
Expand Down
95 changes: 95 additions & 0 deletions pages/dashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type { NextPage } from "next";
import { Fragment } from "react";
import { useIntl } from "react-intl";
import { useQuery, gql } from "@apollo/client";
import toast from "react-hot-toast";
import { TRANSACTION_AMOUNT_AGGREGATE } from "constants/queries";
import { GET_TRANSACTIONS } from "constants/queries";
import Protected from "components/Protected";
import LargeNumberCard from "components/LargeNumberCard";
import SmallNumberCard from "components/SmallNumberCard";
import Transaction from "components/Transaction";
import CategoryChart from "components/CategoryChart";

const Dashboard: NextPage = () => {
const intl = useIntl();
const { data: income, error: incomeError } = useQuery(
TRANSACTION_AMOUNT_AGGREGATE,
{
variables: { filter: { and: [{ amount: { gt: 0 } }] } },
}
);
if (incomeError) {
console.error(incomeError);
toast.error(incomeError.message);
}
const { data: expense, error: expenseError } = useQuery(
TRANSACTION_AMOUNT_AGGREGATE,
{
variables: { filter: { and: [{ amount: { lt: 0 } }] } },
}
);
if (expenseError) {
console.error(expenseError);
toast.error(expenseError.message);
}

const { data: transactions, error } = useQuery(GET_TRANSACTIONS, {
variables: {
order: { desc: "date" },
limit: 30,
},
});
if (error) {
console.error(error);
toast.error(error.message);
}

return (
<Protected>
<div className="max-w-screen-md ml-auto mr-auto">
<div className="flex flex-row">
<div className="w-1/2 mr-2">
<div className="flex flex-col items-center">
<LargeNumberCard />
<div className="w-full flex flex-row justify-between">
<SmallNumberCard
amount={income?.aggregateTransaction.amountSum || 0}
type="income"
/>
<SmallNumberCard
amount={expense?.aggregateTransaction.amountSum || 0}
type="expense"
/>
</div>
</div>
<div className="flex flex-row justify-start">
<CategoryChart />
</div>
</div>
<div className="w-1/2 ml-2 pt-2">
{!transactions?.queryTransaction.length ? (
<p className="w-full mt-2">
{intl.formatMessage({ defaultMessage: "No transactions" })}
</p>
) : (
<Fragment>
{transactions.queryTransaction.map((transaction: any) => (
<Transaction
key={transaction.id}
id={transaction.id}
category={transaction.category}
amount={transaction.amount}
date={transaction.date}
/>
))}
</Fragment>
)}
</div>
</div>
</div>
</Protected>
);
};

export default Dashboard;
Loading

1 comment on commit 8c71674

@vercel
Copy link

@vercel vercel bot commented on 8c71674 Jul 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

ikura – ./

ikura-git-main-zefhub.vercel.app
ikura.app
ikura.vercel.app
ikura-zefhub.vercel.app
www.ikura.app

Please sign in to comment.