Skip to content

Commit

Permalink
add interactive from/to search when adding a tx
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeWent committed Jul 12, 2024
1 parent bcc3a5f commit 9174d35
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 10 deletions.
8 changes: 8 additions & 0 deletions api/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,21 @@
from app.routes.token import token_router
from app.routes.transaction import transaction_router
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from sqlalchemy.exc import SQLAlchemyError

logger = logging.getLogger(__name__)

config: Config = get_config()
app = FastAPI(title=config.app_name, version=config.app_version)
app.add_middleware(
CORSMiddleware,
allow_credentials=True,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)


@app.exception_handler(ApplicationError)
Expand Down
39 changes: 31 additions & 8 deletions ui/app/controllers/transaction.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
from app.external.refinance import get_refinance_api_client
from app.middlewares.auth import token_required
from app.schemas import Transaction
from flask import Blueprint, redirect, render_template, url_for
from flask import Blueprint, jsonify, redirect, render_template, request, url_for
from flask_wtf import FlaskForm
from wtforms import (
BooleanField,
FloatField,
HiddenField,
IntegerField,
StringField,
SubmitField,
)
from wtforms.validators import DataRequired
from wtforms.validators import DataRequired, NumberRange

transaction_bp = Blueprint("transaction", __name__)


class TransactionForm(FlaskForm):
from_entity_id = IntegerField("From Entity ID", validators=[DataRequired()])
to_entity_id = IntegerField("To Entity ID", validators=[DataRequired()])
from_entity_name = StringField("From")
to_entity_name = StringField("To")
from_entity_id = IntegerField("", validators=[DataRequired(), NumberRange(min=1)])
to_entity_id = IntegerField("", validators=[DataRequired(), NumberRange(min=1)])
amount = FloatField("Amount", validators=[DataRequired()])
currency = StringField("Currency", validators=[DataRequired()])
comment = StringField("Comment")
Expand Down Expand Up @@ -51,16 +54,36 @@ def detail(id):
)


@transaction_bp.route("/hx/search", methods=["GET", "POST"])
@token_required
def hx_search():
api = get_refinance_api_client()
entities = api.http(
"GET", "entities", params=dict(name=request.args.get("name"))
).json()["items"]
return render_template("transaction/hx_search_results.jinja2", entities=entities)


@transaction_bp.route("/hx/entity-name/<int:id>")
@token_required
def hx_entity_name(id):
api = get_refinance_api_client()
r = api.http("GET", f"entities/{id}")
if r.status_code == 200:
return jsonify(r.json()), 200
else:
return jsonify({}), 404


@transaction_bp.route("/add", methods=["GET", "POST"])
@token_required
def add():
form = TransactionForm()
if form.validate_on_submit():
api = get_refinance_api_client()
data = form.data
data.pop("csrf_token")
api.http("POST", "transactions", data=data)
return redirect(url_for("transaction.list"))
tx = api.http("POST", "transactions", data=form.data)
if tx.status_code == 200:
return redirect(url_for("transaction.detail", id=tx.json()["id"]))
return render_template("transaction/add.jinja2", form=form)


Expand Down
2 changes: 1 addition & 1 deletion ui/app/external/refinance.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ def __init__(self, token: str) -> None:
def get_refinance_api_client() -> RefinanceAPI:
token = session.get("token")
if token is None:
raise ValueError("Token is required to create RefinanceAPI client")
raise ValueError("Token is required")
return RefinanceAPI(token)
1 change: 1 addition & 0 deletions ui/app/templates/base.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<meta name="theme-color" content="#000">
<title>refinance</title>
<link rel="stylesheet" href="/static/style.css">
<script src="https://unpkg.com/[email protected]"></script>
</head>

<body>
Expand Down
93 changes: 92 additions & 1 deletion ui/app/templates/transaction/add.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,97 @@

{% block content %}
<form method="post">
{{ render_form(form) }}
<table>
<tr>
<td>{{ form.from_entity_name.label }}</td>
<td>
<input id="from_entity_name" type="search" name="name" placeholder="search..."
hx-trigger="input changed delay:500ms, name" hx-get="hx/search"
hx-target=" #from_entity_name_search_results" hx-indicator=".htmx-indicator">
</td>
<td id="from_entity_name_search_results"></td>
</tr>
<tr>
<td></td>
<td>{{ form.from_entity_id(id="from_entity_id", placeholder="id") }}</td>
</tr>

<tr>
<td>{{ form.to_entity_name.label }}</td>
<td>
<input id="to_entity_name" type="search" name="name" placeholder="search..."
hx-trigger="input changed delay:500ms, name" hx-get="hx/search"
hx-target=" #to_entity_name_search_results" hx-indicator=".htmx-indicator">
</td>
<td id="to_entity_name_search_results"></td>
</tr>
<tr>
<td></td>
<td>{{ form.to_entity_id(id="to_entity_id", placeholder="id") }}</td>
</tr>


{% for field in form %}
{% if field.name not in ('from_entity_name', 'to_entity_name', 'from_entity_id', 'to_entity_id') %}
<tr>
{% if field.type not in ('CSRFTokenField', 'SubmitField') %}
<td>{{ field.label() }}</td>
{% endif %}
<td>{{ field() }}</td>
</tr>
{% endif %}
{% endfor %}

</table>
</form>

<script>
document.addEventListener('click', function (event) {
if (event.target.matches('.search-result-item')) {
const name = event.target.getAttribute('x-name');
const id = event.target.getAttribute('x-id');
const searchResultsId = event.target.closest('td').id;
if (searchResultsId === 'from_entity_name_search_results') {
document.getElementById('from_entity_name').value = name;
document.getElementById('from_entity_id').value = id;
document.getElementById('from_entity_id').focus();
} else if (searchResultsId === 'to_entity_name_search_results') {
document.getElementById('to_entity_name').value = name;
document.getElementById('to_entity_id').value = id;
document.getElementById('to_entity_id').focus();
}
document.getElementById(searchResultsId).innerHTML = '';
}
});
document.getElementById('from_entity_id').addEventListener('change', function () {
fetchEntityName('from_entity_id', 'from_entity_name');
});
document.getElementById('to_entity_id').addEventListener('change', function () {
fetchEntityName('to_entity_id', 'to_entity_name');
});
function fetchEntityName(entityIdField, entityNameField) {
const entityId = document.getElementById(entityIdField).value;
if (entityId) {
fetch(`/transactions/hx/entity-name/${entityId}`)
.then(response => response.json())
.then(data => {
if (data.id) {
document.getElementById(entityNameField).value = data.name;
} else {
alert('Entity not found');
}
})
.catch(error => {
console.error('Error fetching entity name:', error);
alert('Error fetching entity name');
});
}
}
</script>
{% endblock %}
3 changes: 3 additions & 0 deletions ui/app/templates/transaction/hx_search_results.jinja2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{% for entity in entities %}
<a href="#" class="search-result-item" x-id="{{ entity.id }}" x-name="{{ entity.name }}">{{ entity.name }}</a>
{% endfor %}

0 comments on commit 9174d35

Please sign in to comment.