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

update customer data app #235

Merged
merged 5 commits into from
Jun 3, 2024
Merged
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
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
Loading