Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: major: add account detail and card history page #1

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions my-app/src/AccountDetails.tsx
Original file line number Diff line number Diff line change
@@ -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<AccountDetailsProps>();
const [accountInfo, setAccountInfo] = useState<AccountInfo>(null);

useEffect(() => {
fetchAccountDetails(id)
}, [id]);


const fetchAccountDetails = async (accountId: string): Promise<void> => {
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 (
<div>
{accountInfo && (
<div>
<h2>Account Details</h2>
<p>Name: {accountInfo.name}</p>
<p>Social Security Number: {accountInfo.socialSecurityNumber}</p>
<p>Date of Birth: {accountInfo.dateOfBirth}</p>
<p>Address: {accountInfo.address}</p>
<p>Credit Score: {accountInfo.creditScore}</p>
<p>Email: {accountInfo.email}</p>
<p>Phone Number: {accountInfo.phoneNumber}</p>
<p>Card Number: {accountInfo.cardNumber}</p>
<PaymentHistoryTable accountId={id} />
</div>
)}
</div>
);
};

export default AccountDetails;
1 change: 1 addition & 0 deletions my-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ function App() {
<Router>
<Switch>
<Route path="/" Component={SearchPage} />
<Route path="/account/:id" Component={AccountDetails} />
</Switch>
</Router>
);
Expand Down
64 changes: 64 additions & 0 deletions my-app/src/PaymentsHistoryTable.tsx
Original file line number Diff line number Diff line change
@@ -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<PaymentHistoryTableProps> = ({ accountId }) => {
const [paymentHistory, setPaymentHistory] = useState<PaymentRecord[]>([]);

const fetchHistory = async (accountId: string): Promise<void> => {
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 (
<div>
<h3>Payment History</h3>
<table>
<thead>
<tr>
<th>Date</th>
<th>Description</th>
<th>Amount</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
{paymentHistory.map((record, index) => (
<tr key={index}>
<td>{record.date}</td>
<td>{record.description}</td>
<td>${record.amount}</td>
<td>${record.balance}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};

export default PaymentHistoryTable;
39 changes: 23 additions & 16 deletions my-app/src/SearchPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, FormEvent, ChangeEvent } from 'react';
import React, { useState, FormEvent } from 'react';
import SearchResultsTable from './SearchResultsTable';

export interface SearchResult {
Expand All @@ -13,50 +13,57 @@ export interface SearchResult {
}

function SearchPage() {
const [searchQuery, setSearchQuery] = useState<string>('');
const [firstName, setFirstName] = useState<string>('');
const [lastName, setLastName] = useState<string>('');
const [cardNumber, setCardNumber] = useState<string>('');
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);


const submitForm = async (): Promise<void> => {
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) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

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<HTMLFormElement>) => {
e.preventDefault();
submitForm()
};

const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value);
submitForm();
};

return (
<div>
<form onSubmit={handleSearch}>
<input
type="text"
placeholder="Search by name, card number, or issuance date"
value={searchQuery}
onChange={handleInputChange}
placeholder="First Name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
/>
<input
type="text"
placeholder="Last Name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
<input
type="text"
placeholder="Card Number"
value={cardNumber}
onChange={(e) => setCardNumber(e.target.value)}
/>
<button type="submit">Search</button>
</form>
Expand Down
5 changes: 5 additions & 0 deletions server/api/card_holder.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@
def search_cardholders():
search_params = request.args
return card_holder_controller.get_card_holders(search_params)


@cardholder_api.route('/cardholders/<int:card_holder_id>/details', methods=['GET'])
def get_cardholder_detail(card_holder_id):
return card_holder_controller.get_card_holder_details(card_holder_id)
2 changes: 1 addition & 1 deletion server/api/credit_card.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@
# Route for getting all details of credit card transactions
@creditcard_api.route('/creditcards/<int:card_holder_id>/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)
21 changes: 19 additions & 2 deletions server/controllers/card_holder_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -17,5 +27,12 @@ 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


card_holder_controller = CardHolderController()
17 changes: 17 additions & 0 deletions server/controllers/credit_card_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ 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()
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()
if credit_card:
Expand All @@ -23,5 +32,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()
9 changes: 9 additions & 0 deletions server/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))