Skip to content

Commit

Permalink
Merge pull request #235 from reflex-dev/carlos/improve-examples
Browse files Browse the repository at this point in the history
update customer data app
  • Loading branch information
tgberkeley committed Jun 3, 2024
2 parents 5dfde37 + 279d492 commit c840c93
Show file tree
Hide file tree
Showing 37 changed files with 2,114 additions and 897 deletions.
34 changes: 34 additions & 0 deletions customer_data_app/alembic/versions/277cad49d2b0_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""empty message
Revision ID: 277cad49d2b0
Revises: 7aaec6b87d88
Create Date: 2024-05-30 10:58:18.235598
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import sqlmodel

# revision identifiers, used by Alembic.
revision: str = '277cad49d2b0'
down_revision: Union[str, None] = '7aaec6b87d88'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('customer', schema=None) as batch_op:
batch_op.add_column(sa.Column('date', sqlmodel.sql.sqltypes.AutoString(), nullable=False))

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('customer', schema=None) as batch_op:
batch_op.drop_column('date')

# ### end Alembic commands ###
36 changes: 36 additions & 0 deletions customer_data_app/alembic/versions/7aaec6b87d88_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""empty message
Revision ID: 7aaec6b87d88
Revises: e565fdc23e6c
Create Date: 2024-05-30 09:50:05.149862
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa
import sqlmodel

# revision identifiers, used by Alembic.
revision: str = '7aaec6b87d88'
down_revision: Union[str, None] = 'e565fdc23e6c'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('customer', schema=None) as batch_op:
batch_op.add_column(sa.Column('payments', sa.Float(), nullable=False))
batch_op.add_column(sa.Column('status', sqlmodel.sql.sqltypes.AutoString(), nullable=False))

# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('customer', schema=None) as batch_op:
batch_op.drop_column('status')
batch_op.drop_column('payments')

# ### end Alembic commands ###
Empty file.
183 changes: 183 additions & 0 deletions customer_data_app/customer_data_app/backend/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import reflex as rx
from typing import Literal, Union
from sqlmodel import select, asc, desc, or_, func
from datetime import datetime, timedelta

#LiteralStatus = Literal["Delivered", "Pending", "Cancelled"]


def _get_percentage_change(value: Union[int, float], prev_value: Union[int, float]) -> float:
percentage_change = (
round(((value - prev_value) / prev_value) * 100, 2)
if prev_value != 0
else 0
if value == 0
else
float("inf")
)
return percentage_change

class Customer(rx.Model, table=True):
"""The customer model."""

name: str
email: str
phone: str
address: str
date: str
payments: float
status: str


class MonthValues(rx.Base):
"""Values for a month."""

num_customers: int = 0
total_payments: float = 0.0
num_delivers: int = 0



class State(rx.State):
"""The app state."""

users: list[Customer] = []
sort_value: str = ""
sort_reverse: bool = False
search_value: str = ""
current_user: Customer = Customer()
# Values for current and previous month
current_month_values: MonthValues = MonthValues()
previous_month_values: MonthValues = MonthValues()


def load_entries(self) -> list[Customer]:
"""Get all users from the database."""
with rx.session() as session:
query = select(Customer)
if self.search_value:
search_value = f"%{str(self.search_value).lower()}%"
query = query.where(
or_(
*[
getattr(Customer, field).ilike(search_value)
for field in Customer.get_fields()
],
)
)

if self.sort_value:
sort_column = getattr(Customer, self.sort_value)
if self.sort_value == "payments":
order = desc(sort_column) if self.sort_reverse else asc(sort_column)
else:
order = desc(func.lower(sort_column)) if self.sort_reverse else asc(func.lower(sort_column))
query = query.order_by(order)

self.users = session.exec(query).all()

self.get_current_month_values()
self.get_previous_month_values()


def get_current_month_values(self):
"""Calculate current month's values."""
now = datetime.now()
start_of_month = datetime(now.year, now.month, 1)

current_month_users = [
user for user in self.users if datetime.strptime(user.date, '%Y-%m-%d %H:%M:%S') >= start_of_month
]
num_customers = len(current_month_users)
total_payments = sum(user.payments for user in current_month_users)
num_delivers = len([user for user in current_month_users if user.status == "Delivered"])
self.current_month_values = MonthValues(num_customers=num_customers, total_payments=total_payments, num_delivers=num_delivers)


def get_previous_month_values(self):
"""Calculate previous month's values."""
now = datetime.now()
first_day_of_current_month = datetime(now.year, now.month, 1)
last_day_of_last_month = first_day_of_current_month - timedelta(days=1)
start_of_last_month = datetime(last_day_of_last_month.year, last_day_of_last_month.month, 1)

previous_month_users = [
user for user in self.users
if start_of_last_month <= datetime.strptime(user.date, '%Y-%m-%d %H:%M:%S') <= last_day_of_last_month
]
# We add some dummy values to simulate growth/decline. Remove them in production.
num_customers = len(previous_month_users) + 3
total_payments = sum(user.payments for user in previous_month_users) + 240
num_delivers = len([user for user in previous_month_users if user.status == "Delivered"]) + 5

self.previous_month_values = MonthValues(num_customers=num_customers, total_payments=total_payments, num_delivers=num_delivers)


def sort_values(self, sort_value: str):
self.sort_value = sort_value
self.load_entries()


def toggle_sort(self):
self.sort_reverse = not self.sort_reverse
self.load_entries()

def filter_values(self, search_value):
self.search_value = search_value
self.load_entries()

def get_user(self, user: Customer):
self.current_user = user


def add_customer_to_db(self, form_data: dict):
self.current_user = form_data
self.current_user["date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

with rx.session() as session:
if session.exec(
select(Customer).where(Customer.email == self.current_user["email"])
).first():
return rx.window_alert("User with this email already exists")
session.add(Customer(**self.current_user))
session.commit()
self.load_entries()
return rx._x.toast.info(f"User {self.current_user["name"]} has been added.", variant="outline", position="bottom-right")


def update_customer_to_db(self, form_data: dict):
self.current_user.update(form_data)
with rx.session() as session:
customer = session.exec(
select(Customer).where(Customer.id == self.current_user["id"])
).first()
for field in Customer.get_fields():
if field != "id":
setattr(customer, field, self.current_user[field])
session.add(customer)
session.commit()
self.load_entries()
return rx._x.toast.info(f"User {self.current_user["name"]} has been modified.", variant="outline", position="bottom-right")


def delete_customer(self, id: int):
"""Delete a customer from the database."""
with rx.session() as session:
customer = session.exec(select(Customer).where(Customer.id == id)).first()
session.delete(customer)
session.commit()
self.load_entries()
return rx._x.toast.info(f"User {customer.name} has been deleted.", variant="outline", position="bottom-right")


@rx.var
def payments_change(self) -> float:
return _get_percentage_change(self.current_month_values.total_payments, self.previous_month_values.total_payments)

@rx.var
def customers_change(self) -> float:
return _get_percentage_change(self.current_month_values.num_customers, self.previous_month_values.num_customers)

@rx.var
def delivers_change(self) -> float:
return _get_percentage_change(self.current_month_values.num_delivers, self.previous_month_values.num_delivers)
Empty file.
26 changes: 26 additions & 0 deletions customer_data_app/customer_data_app/components/form_field.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import reflex as rx


def form_field(
label: str, placeholder: str, type: str, name: str, icon: str, default_value: str = ""
) -> rx.Component:
return rx.form.field(
rx.flex(
rx.hstack(
rx.icon(icon, size=16, stroke_width=1.5),
rx.form.label(label),
align="center",
spacing="2",
),
rx.form.control(
rx.input(
placeholder=placeholder, type=type, default_value=default_value
),
as_child=True,
),
direction="column",
spacing="1",
),
name=name,
width="100%",
)
110 changes: 110 additions & 0 deletions customer_data_app/customer_data_app/components/stats_cards.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import reflex as rx
from reflex.components.radix.themes.base import (
LiteralAccentColor,
)

from ..backend.backend import State


def _arrow_badge(arrow_icon: str, percentage_change: float, arrow_color: str):
return rx.badge(
rx.icon(
tag=arrow_icon,
color=rx.color(arrow_color, 9),
),
rx.text(
f"{percentage_change}%",
size="2",
color=rx.color(arrow_color, 9),
weight="medium",
),
color_scheme=arrow_color,
radius="large",
align_items="center",
)

def stats_card(stat_name: str,
value: int,
prev_value: int,
percentage_change: float,
icon: str,
icon_color: LiteralAccentColor,
extra_char: str = "") -> rx.Component:
return rx.card(
rx.hstack(
rx.vstack(
rx.hstack(
rx.hstack(
rx.icon(
tag=icon,
size=22,
color=rx.color(icon_color, 11),
),
rx.text(
stat_name,
size="4",
weight="medium",
color=rx.color("gray", 11),
),
spacing="2",
align="center",
),
rx.cond(
value > prev_value,
_arrow_badge("trending-up", percentage_change, "grass"),
_arrow_badge("trending-down", percentage_change, "tomato"),
),
justify="between",
width="100%",
),
rx.hstack(
rx.heading(
f"{extra_char}{value:,}",
size="7",
weight="bold",
),
rx.text(
f"from {extra_char}{prev_value:,}",
size="3",
color=rx.color("gray", 10),
),
spacing="2",
align_items="end",
),
align_items="start",
justify="between",
width="100%",
),
align_items="start",
width="100%",
justify="between",
),
size="3",
width="100%",
max_width="22rem",
)


def stats_cards_group() -> rx.Component:
return rx.flex(
stats_card("Total Customers",
State.current_month_values.num_customers,
State.previous_month_values.num_customers,
State.customers_change,
"users", "blue"),
stats_card("Total Payments",
State.current_month_values.total_payments,
State.previous_month_values.total_payments,
State.payments_change,
"dollar-sign", "orange",
"$"),
stats_card("Total Delivers",
State.current_month_values.num_delivers,
State.previous_month_values.num_delivers,
State.delivers_change,
"truck", "ruby"),
spacing="5",
width="100%",
wrap="wrap",
display=["none", "none", "flex"],
)
Loading

0 comments on commit c840c93

Please sign in to comment.