hakeemsyd commited on
Commit
7b74392
1 Parent(s): ecf35ed

Hook transaction endpoint (#3)

Browse files

- Hookup endpoints (cdfba46637c13a009946e4ca39bd05dd49c43d8c)
- statements list (f81dc3429d9e488ce8867ee6d60c969ab764b189)
- setup income and expense statement (2d4e819921a399a386b4165dfdfef3af7aacf192)

app/(home)/dashboard/page.tsx CHANGED
@@ -1,15 +1,14 @@
1
  'use client';
2
  import { Button, Table, Upload, message } from "antd";
3
  import { UploadOutlined } from '@ant-design/icons';
4
- import React from "react";
5
- import { data } from '../../lib/data';
6
- import { UploadChangeParam, UploadFile } from "antd/es/upload";
7
 
8
- const Dashboard = () => {
9
- const transactions = data['transactions'];
10
-
11
- const columns = Object.keys(transactions[0]).map((key: string) => ({ title: key, dataIndex: key, key: key }));
12
- function handleUpload(info: UploadChangeParam<UploadFile<any>>) {
13
  if (info.file.status !== 'uploading') {
14
  console.log(info.file, info.fileList);
15
  }
@@ -18,11 +17,51 @@ const Dashboard = () => {
18
  } else if (info.file.status === 'error') {
19
  message.error(`${info.file.name} file upload failed.`);
20
  }
21
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
  return (
24
  <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
25
- <Upload onChange={handleUpload}>
26
  <Button icon={<UploadOutlined />}>Click to Upload</Button>
27
  </Upload>
28
  <Table dataSource={transactions} columns={columns} />
 
1
  'use client';
2
  import { Button, Table, Upload, message } from "antd";
3
  import { UploadOutlined } from '@ant-design/icons';
4
+ import React, { useEffect, useState } from "react";
5
+ import { UploadProps } from "antd/es/upload";
6
+ import { fielUploadEndpoint, transactionsEndpoint } from "@/app/lib/endpoints";
7
 
8
+ const props: UploadProps = {
9
+ name: 'upload_file',
10
+ action: fielUploadEndpoint,
11
+ onChange(info) {
 
12
  if (info.file.status !== 'uploading') {
13
  console.log(info.file, info.fileList);
14
  }
 
17
  } else if (info.file.status === 'error') {
18
  message.error(`${info.file.name} file upload failed.`);
19
  }
20
+ },
21
+ };
22
+
23
+ const Dashboard = () => {
24
+ const [transactions, setTransactions] = useState([]);
25
+ useEffect(() => {
26
+ (async () => {
27
+ const resp = await fetch(transactionsEndpoint);
28
+ const t = await resp.json();
29
+ setTransactions(t);
30
+ })();
31
+ }, []);
32
+ const columns = [
33
+ {
34
+ title: 'Date',
35
+ dataIndex: 'transaction_date',
36
+ key: 'date',
37
+ },
38
+ {
39
+ title: 'Description',
40
+ dataIndex: 'name_description',
41
+ key: 'description',
42
+ },
43
+ {
44
+ title: 'Amount',
45
+ dataIndex: 'amount',
46
+ key: 'amount',
47
+ },
48
+ {
49
+ title: 'Category',
50
+ dataIndex: 'category',
51
+ key: 'category',
52
+ },
53
+ {
54
+ title: 'Type',
55
+ dataIndex: 'type',
56
+ key: 'type',
57
+ },
58
+ ];
59
+
60
+
61
 
62
  return (
63
  <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
64
+ <Upload {...props}>
65
  <Button icon={<UploadOutlined />}>Click to Upload</Button>
66
  </Upload>
67
  <Table dataSource={transactions} columns={columns} />
app/(home)/reports/page.tsx CHANGED
@@ -1,8 +1,44 @@
1
  'use client';
2
- import React from "react";
 
 
 
 
 
 
 
3
  const Reports = () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  return (
5
- <div>reports route</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  );
7
  }
8
 
 
1
  'use client';
2
+ import { incomeStatements } from "@/app/lib/endpoints";
3
+ import moment from "moment";
4
+ import { List } from "antd";
5
+ import React, { useEffect, useState } from "react";
6
+ import { useRouter, useSearchParams } from "next/navigation";
7
+ import IncomeStatement from "@/app/components/IncomeStatement";
8
+ import { DoubleLeftOutlined } from '@ant-design/icons';
9
+
10
  const Reports = () => {
11
+ const searchParams = useSearchParams()
12
+ const statement_id = searchParams.get('statement_id')
13
+ const [statements, setStatements] = useState([]);
14
+ const router = useRouter();
15
+
16
+ useEffect(() => {
17
+ (async () => {
18
+ const data = await fetch(incomeStatements)
19
+ const statements = await data.json();
20
+ setStatements(statements);
21
+ })();
22
+ }, []);
23
+
24
  return (
25
+ <div style={{ padding: 24 }}>
26
+ <h1>Statements</h1>
27
+ {statement_id ?
28
+ <IncomeStatement statementData={statements.find(({id}) => id === Number(statement_id))} />
29
+ :
30
+ <List
31
+ itemLayout="horizontal"
32
+ dataSource={statements}
33
+ renderItem={(item: any) => (
34
+ <List.Item>
35
+ <List.Item.Meta
36
+ title={<a onClick={() => router.push(`?statement_id=${item.id}`)}>{`${moment(item.date_from).format('MM-DD-YYYY')} until ${moment(item.date_to).format('MM-DD-YYYY')}`}</a>}
37
+ />
38
+ </List.Item>
39
+ )}
40
+ />}
41
+ </div>
42
  );
43
  }
44
 
app/components/IncomeStatement/index.tsx ADDED
@@ -0,0 +1,132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Flex, Spin, Table, Typography } from 'antd';
2
+ import Title from 'antd/es/typography/Title';
3
+ import { useEffect, useState } from 'react';
4
+ import styles from './styles.module.css'
5
+
6
+ type Props = {
7
+ statementData: any | undefined;
8
+ };
9
+
10
+ const IncomeStatement = ({ statementData }: Props) => {
11
+ console.log('statementData', statementData);
12
+ const [data, setData] = useState<any | undefined>(undefined);
13
+ const [profit, setProfit] = useState(0);
14
+ const [incomeTotal, setIncomeTotal] = useState(0);
15
+ const [expensesTotal, setExpensesTotal] = useState(0);
16
+ const [summaryData, setSummaryData] = useState<any[]>([]);
17
+
18
+ useEffect(() => {
19
+ // (async () => {
20
+ // const resp = await fetch(incomeStatements);
21
+ // const t = await resp.json();
22
+ // setData(t);
23
+ // })();
24
+
25
+ if (statementData) {
26
+ const calculateTotal = (obj: any) => Object.values(obj).reduce((acc: any, value: any) => acc + value, 0);
27
+ const aggIncome = calculateTotal(statementData.income);
28
+ const expensesTotal = calculateTotal(statementData.expenses);
29
+ const profit = Number(incomeTotal) - Number(expensesTotal);
30
+
31
+ const incomeData = Object.keys(statementData.income).map((key) => ({
32
+ key,
33
+ category: `Income - ${key.charAt(0).toUpperCase() + key.slice(1)}`,
34
+ amount: statementData.income[key],
35
+ }));
36
+
37
+ const expensesData = Object.keys(statementData.expenses).map((key) => ({
38
+ key,
39
+ category: `Expense - ${key.charAt(0).toUpperCase() + key.slice(1)}`,
40
+ amount: statementData.expenses[key],
41
+ }));
42
+
43
+ const summaryData = [
44
+ ...incomeData,
45
+ ...expensesData,
46
+ {
47
+ key: 'total-income',
48
+ category: 'Total Income',
49
+ amount: incomeTotal,
50
+ },
51
+ {
52
+ key: 'total-expenses',
53
+ category: 'Total Expenses',
54
+ amount: expensesTotal,
55
+ },
56
+ {
57
+ key: 'profit',
58
+ category: 'Profit',
59
+ amount: profit,
60
+ },
61
+ ];
62
+
63
+ setProfit(profit);
64
+ setIncomeTotal(Number(aggIncome));
65
+ setSummaryData(summaryData);
66
+ }
67
+ setData(statementData);
68
+ }, []);
69
+
70
+
71
+
72
+ const columns = [
73
+ {
74
+ title: 'Category',
75
+ dataIndex: 'category',
76
+ key: 'category',
77
+ },
78
+ {
79
+ title: 'Amount',
80
+ dataIndex: 'amount',
81
+ key: 'amount',
82
+ render: (amount: Number) => `$${amount.toFixed(2)}`,
83
+ },
84
+ ];
85
+
86
+ const rowClassName = (record: any) => {
87
+ if (record.key === 'total-income') return styles.totalIncomeRow;
88
+ if (record.key === 'total-expenses') return styles.totalExpensesRow;
89
+ if (record.key === 'profit') return styles.profitRow;
90
+ return '';
91
+ };
92
+
93
+
94
+
95
+ if (!data) {
96
+ return (
97
+ <Flex align="center" gap="middle">
98
+ <Spin size="large" />
99
+ </Flex>
100
+ )
101
+ }
102
+
103
+ return (
104
+ <>
105
+ <Title level={3}>Profit & Loss Statement</Title>
106
+ <Table
107
+ columns={columns}
108
+ dataSource={summaryData}
109
+ pagination={false}
110
+ rowClassName={rowClassName}
111
+ summary={() => (
112
+ <Table.Summary.Row>
113
+ <Table.Summary.Cell index={1} colSpan={1}>Net Profit</Table.Summary.Cell>
114
+ <Table.Summary.Cell index={2}>
115
+ <span style={{
116
+ fontWeight: 'bold',
117
+ backgroundColor: '#f6ffed',
118
+ padding: '2px 4px',
119
+ display: 'inline-block',
120
+ }}>
121
+ ${profit.toFixed(2)}
122
+ </span>
123
+ </Table.Summary.Cell>
124
+ </Table.Summary.Row>
125
+ )}
126
+ />
127
+ </>
128
+ );
129
+ };
130
+
131
+
132
+ export default IncomeStatement
app/components/IncomeStatement/styles.module.css ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .totalIncomeRow{
2
+ background-color: #e6f7ff;
3
+ font-weight: bold;
4
+ }
5
+
6
+ .totalExpenseRow {
7
+ background-color: #fff2e8;
8
+ font-weight: bold;
9
+ }
10
+
11
+ .profitRow {
12
+ background-color: #f6ffed;
13
+ font-weight: bold;
14
+ }
app/lib/endpoints/index.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL;
2
+ const url = `${baseUrl}/api/v1`
3
+
4
+ export const transactionsEndpoint = `${url}/transactions/1`;
5
+ export const fielUploadEndpoint = `${url}/file_upload`;
6
+ export const incomeStatements = `${url}/income_statement/1`;
7
+ export const statementEndpoint = `${url}/statement`;
package-lock.json CHANGED
@@ -22,6 +22,7 @@
22
  "dotenv": "^16.3.1",
23
  "llamaindex": "0.3.13",
24
  "lucide-react": "^0.294.0",
 
25
  "next": "^14.0.3",
26
  "pdf2json": "3.0.5",
27
  "react": "^18.2.0",
@@ -9124,6 +9125,14 @@
9124
  "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
9125
  "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A=="
9126
  },
 
 
 
 
 
 
 
 
9127
  "node_modules/mongodb": {
9128
  "version": "6.6.2",
9129
  "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz",
 
22
  "dotenv": "^16.3.1",
23
  "llamaindex": "0.3.13",
24
  "lucide-react": "^0.294.0",
25
+ "moment": "^2.30.1",
26
  "next": "^14.0.3",
27
  "pdf2json": "3.0.5",
28
  "react": "^18.2.0",
 
9125
  "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
9126
  "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A=="
9127
  },
9128
+ "node_modules/moment": {
9129
+ "version": "2.30.1",
9130
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
9131
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
9132
+ "engines": {
9133
+ "node": "*"
9134
+ }
9135
+ },
9136
  "node_modules/mongodb": {
9137
  "version": "6.6.2",
9138
  "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz",
package.json CHANGED
@@ -25,6 +25,7 @@
25
  "dotenv": "^16.3.1",
26
  "llamaindex": "0.3.13",
27
  "lucide-react": "^0.294.0",
 
28
  "next": "^14.0.3",
29
  "pdf2json": "3.0.5",
30
  "react": "^18.2.0",
 
25
  "dotenv": "^16.3.1",
26
  "llamaindex": "0.3.13",
27
  "lucide-react": "^0.294.0",
28
+ "moment": "^2.30.1",
29
  "next": "^14.0.3",
30
  "pdf2json": "3.0.5",
31
  "react": "^18.2.0",