From f07c07c6d2d3ab3e36127d7bc7bd199afbd64ef2 Mon Sep 17 00:00:00 2001 From: linyahu Date: Fri, 22 Mar 2024 18:21:41 -0400 Subject: [PATCH 1/4] feat: major: add account detail and card history page --- my-app/src/AccountDetails.tsx | 91 ++++++++++++++++++++ my-app/src/App.tsx | 1 + my-app/src/PaymentsHistoryTable.tsx | 64 ++++++++++++++ my-app/src/SearchPage.tsx | 39 +++++---- server/api/card_holder.py | 5 ++ server/api/credit_card.py | 2 +- server/controllers/card_holder_controller.py | 15 ++++ server/controllers/credit_card_controller.py | 13 +++ server/models.py | 9 ++ 9 files changed, 222 insertions(+), 17 deletions(-) create mode 100644 my-app/src/AccountDetails.tsx create mode 100644 my-app/src/PaymentsHistoryTable.tsx diff --git a/my-app/src/AccountDetails.tsx b/my-app/src/AccountDetails.tsx new file mode 100644 index 0000000..68a3471 --- /dev/null +++ b/my-app/src/AccountDetails.tsx @@ -0,0 +1,91 @@ +import React, { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import PaymentHistoryTable from './PaymentHistoryTable'; + +interface AccountDetailsProps { + id: string; +} + +interface AccountInfo { + name: string; + socialSecurityNumber: string; + dateOfBirth: string; + address: string; + creditScore: number; + email: string; + phoneNumber: string; + cardNumber: string; +} + +interface AccountInfo { + id: string; + name: string; + socialSecurityNumber: string; + dateOfBirth: string; + address: string; + creditScore: number; + email: string; + phoneNumber: string; + cardNumber: string; +} + +interface PaymentRecord { + date: string; + description: string; + amount: number; + balance: number; +} + +interface AccountDetails { + accountInfo: AccountInfo; + paymentHistory: PaymentRecord[]; +} + + + +const AccountDetails: React.FC = () => { + const { id } = useParams(); + const [accountInfo, setAccountInfo] = useState(null); + + useEffect(() => { + fetchAccountDetails(id) + }, [id]); + + + const fetchAccountDetails = async (accountId: string): Promise => { + try { + const response = await fetch(`https://api.apg.com/accounts/${accountId}`); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const accountDetails: AccountDetails = await response.json(); + setAccountInfo(accountDetails); + } catch (error) { + console.error('Error fetching account details:', error); + throw error; + } + }; + + return ( +
+ {accountInfo && ( +
+

Account Details

+

Name: {accountInfo.name}

+

Social Security Number: {accountInfo.socialSecurityNumber}

+

Date of Birth: {accountInfo.dateOfBirth}

+

Address: {accountInfo.address}

+

Credit Score: {accountInfo.creditScore}

+

Email: {accountInfo.email}

+

Phone Number: {accountInfo.phoneNumber}

+

Card Number: {accountInfo.cardNumber}

+ +
+ )} +
+ ); +}; + +export default AccountDetails; diff --git a/my-app/src/App.tsx b/my-app/src/App.tsx index 0ed220a..ffa396c 100644 --- a/my-app/src/App.tsx +++ b/my-app/src/App.tsx @@ -9,6 +9,7 @@ function App() { + ); diff --git a/my-app/src/PaymentsHistoryTable.tsx b/my-app/src/PaymentsHistoryTable.tsx new file mode 100644 index 0000000..5bb5eea --- /dev/null +++ b/my-app/src/PaymentsHistoryTable.tsx @@ -0,0 +1,64 @@ +import React, { useEffect, useState } from 'react'; + +interface PaymentHistoryTableProps { + accountId: string; +} + +interface PaymentRecord { + date: string; + description: string; + amount: number; + balance: number; +} + +const PaymentHistoryTable: React.FC = ({ accountId }) => { + const [paymentHistory, setPaymentHistory] = useState([]); + + const fetchHistory = async (accountId: string): Promise => { + try { + const response = await fetch(`https://api.apg.com/creditcards/${accountId}/transactions`); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + const history: PaymentRecord[] = await response.json(); + setPaymentHistory(history); + } catch (error) { + console.error('Error fetching account details:', error); + throw error; + } + }; + + useEffect(() => { + fetchHistory(accountId) + }, [accountId]); + + return ( +
+

Payment History

+ + + + + + + + + + + {paymentHistory.map((record, index) => ( + + + + + + + ))} + +
DateDescriptionAmountBalance
{record.date}{record.description}${record.amount}${record.balance}
+
+ ); +}; + +export default PaymentHistoryTable; diff --git a/my-app/src/SearchPage.tsx b/my-app/src/SearchPage.tsx index 7e2fcd2..e8c1e58 100644 --- a/my-app/src/SearchPage.tsx +++ b/my-app/src/SearchPage.tsx @@ -1,4 +1,4 @@ -import React, { useState, FormEvent, ChangeEvent } from 'react'; +import React, { useState, FormEvent } from 'react'; import SearchResultsTable from './SearchResultsTable'; export interface SearchResult { @@ -13,18 +13,19 @@ export interface SearchResult { } function SearchPage() { - const [searchQuery, setSearchQuery] = useState(''); + const [firstName, setFirstName] = useState(''); + const [lastName, setLastName] = useState(''); + const [cardNumber, setCardNumber] = useState(''); const [searchResults, setSearchResults] = useState([]); - const submitForm = async (): Promise => { try { - const response = await fetch('https://api.afg.com/account-search', { + const response = await fetch('https://api.afg.com/cardholders/search', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify(searchQuery), + body: JSON.stringify({ firstName, lastName, cardNumber }), }); if (!response.ok) { @@ -32,21 +33,15 @@ function SearchPage() { } const results: SearchResult[] = await response.json(); - setSearchResults(results) + setSearchResults(results); } catch (error) { console.error('Error fetching search results:', error); - throw error; } }; - const handleSearch = (e: FormEvent) => { e.preventDefault(); - submitForm() - }; - - const handleInputChange = (e: ChangeEvent) => { - setSearchQuery(e.target.value); + submitForm(); }; return ( @@ -54,9 +49,21 @@ function SearchPage() {
setFirstName(e.target.value)} + /> + setLastName(e.target.value)} + /> + setCardNumber(e.target.value)} />
diff --git a/server/api/card_holder.py b/server/api/card_holder.py index 73826a2..f2644b5 100644 --- a/server/api/card_holder.py +++ b/server/api/card_holder.py @@ -13,3 +13,8 @@ def search_cardholders(): search_params = request.args return card_holder_controller.get_card_holders(search_params) + + +@cardholder_api.route('/cardholders//details', methods=['GET']) +def get_cardholder_detail(card_holder_id): + return card_holder_controller.get_card_holder_details(card_holder_id) diff --git a/server/api/credit_card.py b/server/api/credit_card.py index 71cd3d3..76f68a7 100644 --- a/server/api/credit_card.py +++ b/server/api/credit_card.py @@ -11,4 +11,4 @@ # Route for getting all details of credit card transactions @creditcard_api.route('/creditcards//transactions', methods=['GET']) def get_credit_card_transactions(card_holder_id): - return credit_card_controller.get_credit_cards_by_card_holder(card_holder_id) + return credit_card_controller.get_credit_card_transactions(card_holder_id) diff --git a/server/controllers/card_holder_controller.py b/server/controllers/card_holder_controller.py index b2ec0b5..e9a4537 100644 --- a/server/controllers/card_holder_controller.py +++ b/server/controllers/card_holder_controller.py @@ -17,5 +17,20 @@ def get_card_holders_by_card_issuance_date(self, issuance_date): card_holders = CardHolder.query.join(CreditCard).filter(CreditCard.issuance_date == issuance_date).all() return jsonify([card_holder.to_dict() for card_holder in card_holders]) + def get_card_holder_details(self, card_holder_id): + card_holder = CardHolder.query.get(card_holder_id) + if card_holder: + return jsonify(card_holder.to_dict()) + else: + return jsonify({"error": "Card holder not found"}), 404 + + def get_card_holders_by_name(self, name): + card_holders = CardHolder.query.filter(CardHolder.name.ilike(f'%{name}%')).all() + return jsonify([card_holder.to_dict() for card_holder in card_holders]) + + def get_card_holders_by_card_issuance_date(self, issuance_date): + card_holders = CardHolder.query.join(CreditCard).filter(CreditCard.issuance_date == issuance_date).all() + return jsonify([card_holder.to_dict() for card_holder in card_holders]) + card_holder_controller = CardHolderController() diff --git a/server/controllers/credit_card_controller.py b/server/controllers/credit_card_controller.py index f28a080..2790fdd 100644 --- a/server/controllers/credit_card_controller.py +++ b/server/controllers/credit_card_controller.py @@ -14,6 +14,11 @@ def get_credit_card_by_number(self, card_number): credit_card = CreditCard.query.filter_by(card_number=card_number).first() return jsonify(credit_card.to_dict() if credit_card else {}) + def get_credit_card_balance(self, card_number): + credit_card = CreditCard.query.filter_by(card_number=card_number).first() + return jsonify({'balance': credit_card.current_balance}) if credit_card else jsonify( + {'error': 'Credit card not found'}) + def calculate_next_payment_date(self, card_number): credit_card = CreditCard.query.filter_by(card_number=card_number).first() if credit_card: @@ -23,5 +28,13 @@ def calculate_next_payment_date(self, card_number): else: return jsonify({'error': 'Credit card not found'}) + def get_credit_card_transactions(self, card_number): + credit_card = CreditCard.query.filter_by(card_number=card_number).first() + if credit_card: + transactions = credit_card.transactions + return jsonify([transaction.to_dict() for transaction in transactions]) + else: + return jsonify({'error': 'Credit card not found'}) + credit_card_controller = CreditCardController() diff --git a/server/models.py b/server/models.py index a15fe77..2f6ea93 100644 --- a/server/models.py +++ b/server/models.py @@ -25,3 +25,12 @@ class CreditCard(db.Model): overdue_balance_payment_date = db.Column(db.Date, nullable=False) days_overdue = db.Column(db.Integer, nullable=False) card_holder_id = db.Column(db.Integer, db.ForeignKey('card_holder.id'), nullable=False) + + +class Transaction(db.Model): + id = db.Column(db.Integer, primary_key=True) + credit_card_id = db.Column(db.Integer, db.ForeignKey('credit_card.id'), nullable=False) + transaction_date = db.Column(db.Date, nullable=False) + description = db.Column(db.String(255), nullable=False) + amount = db.Column(db.Float, nullable=False) + credit_card = db.relationship('CreditCard', backref=db.backref('transactions', lazy=True)) From 0a84dc5e735740bb629d5952d5dd04ab469479e2 Mon Sep 17 00:00:00 2001 From: linyahu Date: Fri, 22 Mar 2024 18:22:24 -0400 Subject: [PATCH 2/4] fixing duplicate code --- server/controllers/card_holder_controller.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/server/controllers/card_holder_controller.py b/server/controllers/card_holder_controller.py index e9a4537..f5caeb8 100644 --- a/server/controllers/card_holder_controller.py +++ b/server/controllers/card_holder_controller.py @@ -24,13 +24,5 @@ def get_card_holder_details(self, card_holder_id): else: return jsonify({"error": "Card holder not found"}), 404 - def get_card_holders_by_name(self, name): - card_holders = CardHolder.query.filter(CardHolder.name.ilike(f'%{name}%')).all() - return jsonify([card_holder.to_dict() for card_holder in card_holders]) - - def get_card_holders_by_card_issuance_date(self, issuance_date): - card_holders = CardHolder.query.join(CreditCard).filter(CreditCard.issuance_date == issuance_date).all() - return jsonify([card_holder.to_dict() for card_holder in card_holders]) - card_holder_controller = CardHolderController() From 7a294b84f25a6ac981c81607f033c8f89940e1c9 Mon Sep 17 00:00:00 2001 From: linyahu Date: Fri, 22 Mar 2024 18:30:06 -0400 Subject: [PATCH 3/4] fixing queries --- .idea/workspace.xml | 815 +++++++++++++++++++ server/controllers/card_holder_controller.py | 14 +- server/controllers/credit_card_controller.py | 8 +- 3 files changed, 833 insertions(+), 4 deletions(-) create mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..5dbb4e4 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,815 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1711140791169 + + + 1711140879018 + + + 1711142214148 + + + 1711142459139 + + + 1711142623684 + + + 1711143323104 + + + 1711143405253 + + + 1711143612317 + + + 1711143995578 + + + 1711144334479 + + + 1711144826624 + + + 1711145186145 + + + 1711145448682 + + + 1711145627797 + + + 1711145741582 + + + 1711145869384 + + + 1711146103025 + + + 1711146145255 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/controllers/card_holder_controller.py b/server/controllers/card_holder_controller.py index f5caeb8..1c63ca4 100644 --- a/server/controllers/card_holder_controller.py +++ b/server/controllers/card_holder_controller.py @@ -6,8 +6,18 @@ class CardHolderController(BaseController): model = CardHolder - def get_card_holders(self, search_params): - return self.get_all(**search_params) + def get_card_holders(self, first_name=None, last_name=None, card_number=None): + query = CardHolder.query + + if first_name: + query = query.filter(CardHolder.first_name.ilike(f'%{first_name}%')) + if last_name: + query = query.filter(CardHolder.last_name.ilike(f'%{last_name}%')) + if card_number: + query = query.join(CreditCard).filter(CreditCard.card_number.ilike(f'%{card_number}%')) + + card_holders = query.all() + return jsonify([card_holder.to_dict() for card_holder in card_holders]) def get_card_holders_by_name(self, name): card_holders = CardHolder.query.filter(CardHolder.name.ilike(f'%{name}%')).all() diff --git a/server/controllers/credit_card_controller.py b/server/controllers/credit_card_controller.py index 2790fdd..5403b67 100644 --- a/server/controllers/credit_card_controller.py +++ b/server/controllers/credit_card_controller.py @@ -16,8 +16,12 @@ def get_credit_card_by_number(self, card_number): def get_credit_card_balance(self, card_number): credit_card = CreditCard.query.filter_by(card_number=card_number).first() - return jsonify({'balance': credit_card.current_balance}) if credit_card else jsonify( - {'error': 'Credit card not found'}) + if credit_card: + # Sum up the amounts of all transactions related to the credit card + total_balance = sum(transaction.amount for transaction in credit_card.transactions) + return jsonify({'balance': total_balance}) + else: + return jsonify({'error': 'Credit card not found'}) def calculate_next_payment_date(self, card_number): credit_card = CreditCard.query.filter_by(card_number=card_number).first() From ac06fdcffd0fde281f421a22386ca8e31188e664 Mon Sep 17 00:00:00 2001 From: linyahu Date: Fri, 22 Mar 2024 18:30:53 -0400 Subject: [PATCH 4/4] remove file --- .idea/workspace.xml | 815 -------------------------------------------- 1 file changed, 815 deletions(-) delete mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 5dbb4e4..0000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,815 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1711140791169 - - - 1711140879018 - - - 1711142214148 - - - 1711142459139 - - - 1711142623684 - - - 1711143323104 - - - 1711143405253 - - - 1711143612317 - - - 1711143995578 - - - 1711144334479 - - - 1711144826624 - - - 1711145186145 - - - 1711145448682 - - - 1711145627797 - - - 1711145741582 - - - 1711145869384 - - - 1711146103025 - - - 1711146145255 - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file