Files
TMS/transportmanager/client/pages/dashboard_page.py

497 lines
18 KiB
Python

import flet as ft
from flet import canvas
import requests
from pages.clients_page import ClientsPage
from pages.transporters_page import TransportersPage
from pages.destinations_page import DestinationsPage
from pages.orders_page import OrdersPage
from pages.reports_page import ReportsPage
from pages.profile_page import ProfilePage
from pages.subscription_page import Subscription
from datetime import datetime
from pages.admin_page import Admin
from config import API_BASE_URL
from email.utils import parsedate_to_datetime
class DashboardPage:
def __init__(self, page: ft.Page):
self.page = page
self.placeholder = ft.Container(
expand=True,
padding=ft.padding.only(
top=30,
left=20,
right=20,
bottom=20
),
)
self.all_clients = self.get_all_clients()
self.all_orders = self.get_all_orders()
self.all_transporters = self.get_all_transporters()
self.orders = []
self.profit = self.get_profit()
self.logo = ft.Image(
src=f"images/{self.page.client_storage.get('logo_filename') or 'truck_logo_black.png'}",
width=60,
height=60,
fit=ft.ImageFit.CONTAIN
)
self.subscription_status = ft.Text(
"",
color=ft.Colors.RED
)
self.user = self.get_user()
# --- helper to parse created_at values coming from API in multiple formats ---
def _parse_dt(self, s):
if not s:
return None
if isinstance(s, datetime):
return s
try:
# First try ISO 8601 (with or without microseconds)
return datetime.fromisoformat(s.replace("Z", "+00:00"))
except Exception:
pass
try:
# Then try RFC 2822 / RFC 1123 (e.g., 'Mon, 18 Aug 2025 19:22:19 GMT')
return parsedate_to_datetime(s)
except Exception:
pass
try:
# Fallback: common ISO without microseconds
return datetime.strptime(s, "%Y-%m-%dT%H:%M:%S")
except Exception:
return None
def get_user(self):
token = self.page.client_storage.get("token")
if not token:
self.message.value = "Unauthorized: No token"
return
response = requests.get(f"{API_BASE_URL}/profile/", headers={"Authorization": f"Bearer {token}"})
#print(response.text)
return response.json() if response.status_code == 200 else None
def on_admin_btn_click(self, e):
admin = Admin(self.page, self)
self.placeholder.content = admin.build()
self.placeholder.update()
def get_all_transporters(self):
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{API_BASE_URL}/transporters/", headers=headers)
return response.json() if response.status_code == 200 else []
except Exception as e:
print("Error loading transporters:", e)
def get_all_orders(self):
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{API_BASE_URL}/orders/list", headers=headers)
return response.json() if response.status_code == 200 else []
except Exception as e:
print("Error loading orders:", e)
def get_all_clients(self):
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{API_BASE_URL}/clients/", headers=headers)
return response.json() if response.status_code == 200 else []
except Exception as e:
print("Error loading clients:", e)
def get_profit(self):
date_today = datetime.now()
month_first_day = date_today.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
#print(month_first_day)
if len(self.all_orders)>0:
for order in self.all_orders:
if order.get('status') != 'active':
continue
dt = self._parse_dt(order.get('created_at'))
if dt and dt.date() >= month_first_day.date():
self.orders.append(order)
all_profit = 0
for order in self.orders:
profit = round(order['received_price'] - order['paid_price'], 2)
all_profit += profit
return all_profit
else:
return 0
def _build_dashboard_content(self):
self.all_clients = self.get_all_clients()
self.all_orders = self.get_all_orders()
self.all_transporters = self.get_all_transporters()
self.orders = []
self.profit = self.get_profit()
summary_cards = ft.Row(
controls=[
#self._summary_card("Clients", len(self.all_clients), ft.Icons.PEOPLE),
#self._summary_card("Transporters", len(self.all_transporters), ft.Icons.LOCAL_SHIPPING),
self._summary_card("Monthly Orders", len(self.orders), ft.Icons.RECEIPT_LONG),
self._summary_card("Monthly Profit", self.profit, ft.Icons.SHOW_CHART),
],
spacing=20,
wrap=True
)
# Prepare data for BarChart using flet_charts
from collections import defaultdict
daily_orders = defaultdict(int)
for order in self.orders:
try:
dt = self._parse_dt(order.get('created_at'))
if dt:
daily_orders[dt.day] += 1
except Exception as e:
print("Invalid date:", order['created_at'])
max_value = max(daily_orders.values(), default=1)
bar_groups = []
for day in sorted(daily_orders.keys()):
value = daily_orders[day]
bar_groups.append(
ft.BarChartGroup(
x=day,
bar_rods=[
ft.BarChartRod(
from_y=0,
to_y=value,
width=20,
color=ft.Colors.BLUE
)
]
)
)
chart_1 = ft.Container(
expand=True,
bgcolor=ft.Colors.BLUE_50,
padding=10,
border_radius=20,
content=ft.Column([
ft.Text("Monthly Orders", size=16, weight=ft.FontWeight.BOLD),
ft.BarChart(
bar_groups=bar_groups,
border=ft.border.all(1, ft.Colors.GREY_300),
left_axis=ft.ChartAxis(
labels_size=40,
labels=[
ft.ChartAxisLabel(
value=i,
label=ft.Text(str(i), size=10)
) for i in range(0, max_value + 1, max(1, max_value // 5))
],
),
bottom_axis=ft.ChartAxis(
labels=[
ft.ChartAxisLabel(
value=day,
label=ft.Text(str(day), size=10)
) for day in sorted(daily_orders.keys())
],
),
tooltip_bgcolor=ft.Colors.with_opacity(0.8, ft.Colors.BLUE),
expand=True,
animate=True
)
])
)
# Calculate daily profit
from collections import defaultdict
daily_profit = defaultdict(float)
for order in self.orders:
try:
dt = self._parse_dt(order.get('created_at'))
if dt:
profit = order["received_price"] - order["paid_price"]
daily_profit[dt.day] += profit
except Exception as e:
print("Invalid date in profit chart:", order['created_at'])
max_profit = max(daily_profit.values(), default=1)
profit_bar_groups = []
for day in sorted(daily_profit.keys()):
value = daily_profit[day]
profit_bar_groups.append(
ft.BarChartGroup(
x=day,
bar_rods=[
ft.BarChartRod(
from_y=0,
to_y=value,
width=20,
color=ft.Colors.GREEN
)
]
)
)
chart_2 = ft.Container(
expand=True,
bgcolor=ft.Colors.BLUE_50,
padding=10,
border_radius=20,
content=ft.Column([
ft.Text("Daily Profit", size=16, weight=ft.FontWeight.BOLD),
ft.BarChart(
bar_groups=profit_bar_groups,
border=ft.border.all(1, ft.Colors.GREY_300),
left_axis=ft.ChartAxis(
labels_size=40,
labels=[
ft.ChartAxisLabel(
value=i,
label=ft.Text(str(i), size=10)
) for i in range(0, int(max_profit)+1, max(1, int(max_profit)//5))
],
),
bottom_axis=ft.ChartAxis(
labels=[
ft.ChartAxisLabel(
value=day,
label=ft.Text(str(day), size=10)
) for day in sorted(daily_profit.keys())
],
),
tooltip_bgcolor=ft.Colors.with_opacity(0.8, ft.Colors.GREEN),
expand=True,
animate=True
)
])
)
charts = ft.Row(
controls=[chart_1, chart_2],
expand=True,
spacing=20
)
if self.user:
if self.get_subscription():
if self.get_subscription()['end_date'] < datetime.today()-3:
self.subscription_status.value = "Your subscription will expire soon. Please Renew!"
self.subscription_status.update()
elif self.get_subscription()['end_date'] > datetime.today():
self.subscription_status.value = "Your subscription has expired!"
self.subscription_status.update()
return ft.Column(
[
ft.Row(
[
summary_cards,
self.subscription_status
],
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
),
charts
],
spacing=20,
expand=True
)
else:
return ft.Column(
controls=[
summary_cards,
charts
],
spacing=20,
expand=True
)
else:
return ft.Column(
controls=[
summary_cards,
charts
],
spacing=20,
expand=True
)
def _summary_card(self, title, value, icon):
return ft.Container(
content=ft.Column(
controls=[
ft.Icon(name=icon, size=30),
ft.Text(value, weight=ft.FontWeight.BOLD, size=18),
ft.Text(title, size=14)
],
spacing=5,
alignment=ft.MainAxisAlignment.CENTER
),
width=150,
height=100,
padding=10,
alignment=ft.alignment.center,
border_radius=10,
bgcolor=ft.Colors.BLUE_100
)
def _navigate(self, index):
self.placeholder.content.clean()
if index == 0:
self.placeholder.content = self._build_dashboard_content()
elif index == 1:
client = ClientsPage(self.page, self)
self.placeholder.content = client.build()
elif index == 2:
transporters = TransportersPage(self.page, self)
self.placeholder.content = transporters.build()
elif index == 3:
destinations = DestinationsPage(self.page, self)
self.placeholder.content = destinations.build()
elif index == 4:
orders = OrdersPage(self.page, self)
self.placeholder.content = orders.build()
elif index == 5:
reports = ReportsPage(self.page, self)
self.placeholder.content = reports.build()
elif index == 6:
profile = ProfilePage(self.page, self)
self.placeholder.content = profile.build()
elif index == 7:
subscription = Subscription(self.page, self)
self.placeholder.content = subscription.build()
self.placeholder.update()
def got_to_profile(self, e):
self.placeholder.content.clean()
profile = ProfilePage(self.page, self)
self.placeholder.content = profile.build()
self.placeholder.update()
def _navigate_or_logout(self, index):
if index == 8: # Index of the "Logout" destination
self._logout()
else:
self._navigate(index)
def _logout(self):
self.page.client_storage.remove("token")
self.page.session.clear()
self.page.go("/auth")
def get_subscription(self):
try:
user_id = self.page.session.get("user_id")
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{API_BASE_URL}/admin/subscriptions/{user_id}", headers=headers)
return response.json() if response.status_code == 200 else None
except Exception as e:
print("Error loading clients:", e)
def get_current_subscription_plan(self):
try:
token = self.page.client_storage.get("token")
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(f"{API_BASE_URL}/subscription/", headers=headers)
#print(response.text)
return response.json()[-1] if response.status_code == 200 else None
except Exception as e:
print("Error loading subscription:", e)
def build(self):
self.status = {
'active':'Active',
'cancelled':'Cancelled',
'expired':'Expired',
'less_than_5_days':'Less than 5 days'
}
self.subscription = self.get_current_subscription_plan()
if self.subscription:
self.subscription_status_bottom = ft.Text(
f'{self.status[self.subscription['status']] if self.subscription else "None"}',
size=12,
weight=ft.FontWeight.BOLD,
color=ft.Colors.GREEN if self.subscription['status'] != 'expired' else ft.Colors.RED
)
else:
self.subscription_status_bottom = ft.Text(
f'{self.status[self.subscription['status']] if self.subscription else "None"}',
size=12,
weight=ft.FontWeight.BOLD,
color=ft.Colors.RED
)
nav_rail = ft.NavigationRail(
selected_index=0,
label_type=ft.NavigationRailLabelType.ALL,
on_change=lambda e: self._navigate_or_logout(e.control.selected_index),
destinations=[
ft.NavigationRailDestination(
icon=ft.Icons.DASHBOARD, label="Dashboard"
),
ft.NavigationRailDestination(
icon=ft.Icons.PEOPLE, label="Clients"
),
ft.NavigationRailDestination(
icon=ft.Icons.LOCAL_SHIPPING, label="Transporters"
),
ft.NavigationRailDestination(
icon=ft.Icons.LOCATION_ON, label="Address"
),
ft.NavigationRailDestination(
icon=ft.Icons.RECEIPT_LONG, label="Orders"
),
ft.NavigationRailDestination(
icon=ft.Icons.ASSESSMENT, label="Reports"
),
ft.NavigationRailDestination(
icon=ft.Icons.MANAGE_ACCOUNTS, label="Profile"
),
ft.NavigationRailDestination(
icon=ft.Icons.AUTORENEW, label="Subscription"
),
ft.NavigationRailDestination(
icon=ft.Icons.LOGOUT, label="Logout"
)
],
leading=ft.Container(
content=ft.Column(
controls=[self.logo],
alignment=ft.MainAxisAlignment.CENTER,
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
spacing=5,
),
padding=10
),
#bgcolor = ft.Colors.BLUE,
trailing = ft.Column(
[
ft.Text(
f'\nSubsctiption:',
size=12,
),
self.subscription_status_bottom
],
horizontal_alignment=ft.CrossAxisAlignment.CENTER
)
)
self.placeholder.content = self._build_dashboard_content()
return ft.Row(
controls=[
nav_rail,
ft.VerticalDivider(),
self.placeholder
],
expand=True
)