init commit
This commit is contained in:
147
transportmanager/client/pages/admin_page.py
Normal file
147
transportmanager/client/pages/admin_page.py
Normal file
@@ -0,0 +1,147 @@
|
||||
import flet as ft
|
||||
from pages.admin_tenants_page import Tenants
|
||||
from pages.admin_subscriptions_page import Subscriptions
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class Admin:
|
||||
def __init__(self, page: ft.Page):
|
||||
self.page = page
|
||||
self.placeholder = ft.Column(expand=True)
|
||||
self.total_tenants = ft.Text("Total tenants: 0")
|
||||
self.revenues = ft.Text("Total revenue: 0")
|
||||
|
||||
def get_subscriptions(self):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/admin/subscriptions", headers=headers)
|
||||
return response.json() if response.status_code == 200 else []
|
||||
except Exception as e:
|
||||
print("Error loading clients:", e)
|
||||
|
||||
def build_dashboard(self):
|
||||
all_subscriptions = self.get_subscriptions()
|
||||
total_tenants = len(all_subscriptions)
|
||||
self.total_tenants.value = f"Total tenants: {total_tenants}"
|
||||
sum = 0
|
||||
for subscription in all_subscriptions:
|
||||
if subscription['status'] == 'active':
|
||||
if subscription['plan'] == 'first_2_months':
|
||||
sum += 0
|
||||
elif subscription['plan'] == 'monthly':
|
||||
sum += 100
|
||||
elif subscription['plan'] == 'yearly':
|
||||
sum += 1000
|
||||
self.revenues.value = f"Total revenue: {sum}"
|
||||
return ft.Row(
|
||||
[
|
||||
ft.Container(
|
||||
content = ft.Column(
|
||||
[
|
||||
ft.Icon(name=ft.Icons.PEOPLE, size=50),
|
||||
self.total_tenants,
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
),
|
||||
bgcolor=ft.Colors.BLUE_100,
|
||||
border_radius=20,
|
||||
padding=20
|
||||
),
|
||||
ft.Container(
|
||||
content = ft.Column(
|
||||
[
|
||||
ft.Icon(name=ft.Icons.AUTORENEW, size=50),
|
||||
self.revenues,
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
),
|
||||
bgcolor=ft.Colors.BLUE_100,
|
||||
border_radius=20,
|
||||
padding=20
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def on_tenants_btn_click(self, e):
|
||||
tenents = Tenants(self.page)
|
||||
self.placeholder.controls = [tenents.build()]
|
||||
self.placeholder.update()
|
||||
|
||||
def on_subscription_btn_click(self, e):
|
||||
subscription = Subscriptions(self.page)
|
||||
self.placeholder.controls = [subscription.build()]
|
||||
self.placeholder.update()
|
||||
|
||||
def on_dashboard_btn_click(self, e):
|
||||
self.placeholder.controls = [
|
||||
ft.Text("Admin Dashboard", size=24, weight=ft.FontWeight.BOLD),
|
||||
self.build_dashboard()
|
||||
]
|
||||
self.placeholder.update()
|
||||
|
||||
def on_logout_btn_click(self, e):
|
||||
self.page.client_storage.remove("token")
|
||||
self.page.session.clear()
|
||||
self.page.go("/auth")
|
||||
|
||||
def build(self):
|
||||
self.placeholder.controls = [
|
||||
ft.Text("Admin Dashboard", size=24, weight=ft.FontWeight.BOLD),
|
||||
self.build_dashboard()
|
||||
]
|
||||
return ft.Container(
|
||||
content= ft.Row(
|
||||
[
|
||||
ft.Column(
|
||||
[
|
||||
ft.Column(
|
||||
[
|
||||
ft.Button(
|
||||
"Dashboard",
|
||||
on_click=self.on_dashboard_btn_click,
|
||||
width=150,
|
||||
icon=ft.Icons.DASHBOARD
|
||||
),
|
||||
ft.Button(
|
||||
"Tenents",
|
||||
on_click= self.on_tenants_btn_click,
|
||||
width=150,
|
||||
icon=ft.Icons.PEOPLE
|
||||
),
|
||||
ft.Button(
|
||||
"Subscriptions",
|
||||
on_click=self.on_subscription_btn_click,
|
||||
width=150,
|
||||
icon=ft.Icons.AUTORENEW
|
||||
)
|
||||
]
|
||||
),
|
||||
ft.Button(
|
||||
"Logout",
|
||||
on_click=self.on_logout_btn_click,
|
||||
width=150,
|
||||
icon=ft.Icons.LOGOUT
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
#width=180
|
||||
),
|
||||
ft.VerticalDivider(),
|
||||
ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
self.placeholder,
|
||||
]
|
||||
),
|
||||
expand=True
|
||||
)
|
||||
],
|
||||
expand=True
|
||||
),
|
||||
expand=True,
|
||||
padding=20
|
||||
)
|
||||
160
transportmanager/client/pages/admin_subscriptions_page.py
Normal file
160
transportmanager/client/pages/admin_subscriptions_page.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from config import API_BASE_URL
|
||||
|
||||
class Subscriptions:
|
||||
def __init__(self, page: ft.Page):
|
||||
self.page = page
|
||||
self.plan = {
|
||||
'first_2_months':'First Two Months' ,
|
||||
'monthly':'Monthly',
|
||||
'yearly':'Yearly'
|
||||
}
|
||||
self.status = {
|
||||
'active':'Active',
|
||||
'cancelled':'Cancelled',
|
||||
'expired':'Expired',
|
||||
'less_than_5_days':'Less than 5 days'
|
||||
}
|
||||
self.search_field = ft.TextField(label="Search", on_submit=self.on_search_btn_click, expand=True)
|
||||
self.all_subscriptions = self.get_subscriptions()
|
||||
|
||||
self.subscriptions_list = ft.ListView(
|
||||
controls=self.create_list(self.all_subscriptions, self.on_subscription_btn_click),
|
||||
spacing=10,
|
||||
expand=4
|
||||
)
|
||||
|
||||
self.company_name = ft.TextField(label="Company Name", read_only=True)
|
||||
self.company_register_number = ft.TextField(label="Register Number", read_only=True)
|
||||
self.subscription_plan = ft.TextField(label="Subscription Plan", read_only=True)
|
||||
self.subscription_status = ft.TextField(label="Subscription Status", read_only=True)
|
||||
self.subscription_start_date = ft.TextField(label="Subscription Start Date", read_only=True)
|
||||
self.subscription_end_date = ft.TextField(label="Subscription End Date", read_only=True)
|
||||
|
||||
self.selected_subscription = None
|
||||
|
||||
def create_list(self, items, on_click_handler):
|
||||
"""Helper to create list items for a column."""
|
||||
return [
|
||||
ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text(item['register_number'], expand=True, weight=ft.FontWeight.BOLD),
|
||||
ft.Text(self.plan[item['plan']], size=12)
|
||||
]
|
||||
),
|
||||
ft.Text(self.status[item['status']])
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
width=300,
|
||||
bgcolor=ft.Colors.BLUE_50 if item['status'] != 'expired' else ft.Colors.RED,
|
||||
padding=10,
|
||||
border_radius=8,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
ink=True, # To enable click effect
|
||||
on_click=lambda e, id=item: on_click_handler(id), # Attach the click handler
|
||||
)
|
||||
for item in items
|
||||
]
|
||||
|
||||
def on_subscription_btn_click(self, item):
|
||||
self.selected_subscription = item
|
||||
tenant = self.get_tenant(item['user_id'])
|
||||
self.company_name.value = tenant['name']
|
||||
self.company_name.update()
|
||||
self.company_register_number.value = tenant['register_number']
|
||||
self.company_register_number.update()
|
||||
self.subscription_plan.value = self.plan[item['plan']]
|
||||
self.subscription_plan.update()
|
||||
self.subscription_status.value = self.status[item['status']]
|
||||
self.subscription_status.update()
|
||||
self.subscription_start_date.value = str(item['start_date']).split('T')[0]
|
||||
self.subscription_start_date.update()
|
||||
self.subscription_end_date.value = str(item['end_date']).split('T')[0]
|
||||
self.subscription_end_date.update()
|
||||
|
||||
def get_tenant(self, id):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/admin/users/{id}", headers=headers)
|
||||
return response.json() if response.status_code == 200 else None
|
||||
except Exception as e:
|
||||
print("Error loading clients:", e)
|
||||
|
||||
def get_subscriptions(self):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get("{API_BASE_URL}/admin/subscriptions", headers=headers)
|
||||
return response.json() if response.status_code == 200 else []
|
||||
except Exception as e:
|
||||
print("Error loading clients:", e)
|
||||
|
||||
def on_search_btn_click(self, e):
|
||||
value = self.search_field.value
|
||||
print(f'Search for {value}')
|
||||
buffer = []
|
||||
for subscription in self.all_subscriptions:
|
||||
if value in subscription['register_number']:
|
||||
buffer.append(subscription)
|
||||
self.subscriptions_list.controls.clear()
|
||||
self.subscriptions_list.controls = self.create_list(buffer, self.on_subscription_btn_click)
|
||||
self.subscriptions_list.update()
|
||||
|
||||
def update_status(self, status):
|
||||
try:
|
||||
user_data = {
|
||||
'subscription_id': self.selected_subscription['id'],
|
||||
'status': status
|
||||
}
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post("{API_BASE_URL}/admin/users/update", headers=headers, json = user_data)
|
||||
#print(response.text)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("Subscriptions", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Row(
|
||||
[
|
||||
self.search_field,
|
||||
ft.Button("Search", on_click=self.on_search_btn_click)
|
||||
]
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
self.subscriptions_list,
|
||||
ft.Column(
|
||||
[
|
||||
self.company_name,
|
||||
self.company_register_number,
|
||||
self.subscription_plan,
|
||||
self.subscription_status,
|
||||
self.subscription_start_date,
|
||||
self.subscription_end_date,
|
||||
ft.Row(
|
||||
[
|
||||
ft.Button("Renew", on_click=self.update_status('active')),
|
||||
ft.Button("Unsubscribe", on_click=self.update_status('cancelled'))
|
||||
]
|
||||
)
|
||||
],
|
||||
expand=6
|
||||
)
|
||||
],
|
||||
vertical_alignment=ft.CrossAxisAlignment.START
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
)
|
||||
)
|
||||
174
transportmanager/client/pages/admin_tenants_page.py
Normal file
174
transportmanager/client/pages/admin_tenants_page.py
Normal file
@@ -0,0 +1,174 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import json
|
||||
from config import API_BASE_URL
|
||||
|
||||
class Tenants:
|
||||
def __init__(self, page):
|
||||
self.page = page
|
||||
self.search_field = ft.TextField(label="Search", on_submit=self.on_search_btn_click, expand=True)
|
||||
self.all_tenants = self.get_all_tenants()
|
||||
self.tenants_list = ft.ListView(
|
||||
controls=self.create_list(self.all_tenants, self.on_tenant_btn_click),
|
||||
spacing=10,
|
||||
expand=4
|
||||
)
|
||||
self.name = ft.TextField(label="Company Name")
|
||||
self.address = ft.TextField(label="Company Address")
|
||||
self.contact_name = ft.TextField(label="Contact Name")
|
||||
self.email = ft.TextField(label="Email")
|
||||
self.phone = ft.TextField(label="Phone")
|
||||
self.first_order_number = ft.TextField(label="First Order Number")
|
||||
self.register_number = ft.TextField(label="Register Number")
|
||||
self.terms = ft.TextField(label="Terms and Conditions", multiline=True, min_lines=5, max_lines=10)
|
||||
self.logo_filename = ft.TextField(label="Logo")
|
||||
self.save = ft.Button("Save", on_click=self.on_save_btn_click, width=100)
|
||||
self.error = ft.Text("")
|
||||
self.user_id = None
|
||||
self.user_details = ft.Column(
|
||||
[
|
||||
self.name,
|
||||
self.address,
|
||||
self.contact_name,
|
||||
self.email,
|
||||
self.phone,
|
||||
self.first_order_number,
|
||||
self.register_number,
|
||||
self.logo_filename,
|
||||
self.terms,
|
||||
self.save,
|
||||
self.error
|
||||
],
|
||||
expand=6
|
||||
)
|
||||
|
||||
def on_save_btn_click(self, e):
|
||||
user_data = {
|
||||
'name': self.name.value,
|
||||
'contact_name': self.contact_name.value,
|
||||
'email': self.email.value,
|
||||
'phone': self.phone.value,
|
||||
'register_number': self.register_number.value,
|
||||
'address': self.address.value,
|
||||
'logo_filename': self.logo_filename.value,
|
||||
'terms': self.terms.value,
|
||||
'first_order_number': self.first_order_number.value,
|
||||
'user_id': self.user_id
|
||||
}
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{API_BASE_URL}/admin/users/update", headers=headers, json = user_data)
|
||||
if response.status_code == 200:
|
||||
self.error.value = 'Tenant data saved!'
|
||||
self.error.color = ft.Colors.GREEN
|
||||
self.error.update()
|
||||
else:
|
||||
self.error.value = 'Tenant data not saved!'
|
||||
self.error.color = ft.Colors.RED
|
||||
self.error.update()
|
||||
except Exception as e:
|
||||
print("Error saving tenant data:", e)
|
||||
|
||||
def on_search_btn_click(self, e):
|
||||
value = self.search_field.value
|
||||
print(f'Search for {value}')
|
||||
buffer = []
|
||||
for tenant in self.all_tenants:
|
||||
if value in tenant['name']:
|
||||
buffer.append(tenant)
|
||||
self.tenants_list.controls.clear()
|
||||
self.tenants_list.controls = self.create_list(buffer, self.on_tenant_btn_click)
|
||||
self.tenants_list.update()
|
||||
|
||||
def on_tenant_btn_click(self, item):
|
||||
self.error.value = ''
|
||||
self.error.color = ft.Colors.RED
|
||||
self.error.update()
|
||||
self.name.value = item['name']
|
||||
self.name.update()
|
||||
self.address.value = item['address']
|
||||
self.address.update()
|
||||
self.contact_name.value = item['contact_name']
|
||||
self.contact_name.update()
|
||||
self.email.value = item['email']
|
||||
self.email.update()
|
||||
self.phone.value = item['phone']
|
||||
self.phone.update()
|
||||
self.first_order_number.value = item['first_order_number']
|
||||
self.first_order_number.update()
|
||||
self.register_number.value = item['register_number']
|
||||
self.register_number.update()
|
||||
self.terms.value = item['terms']
|
||||
self.terms.update()
|
||||
self.logo_filename.value = item['logo_filename']
|
||||
self.logo_filename.update()
|
||||
self.user_id = item['id']
|
||||
|
||||
def get_all_tenants(self):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/admin/users", headers=headers)
|
||||
return response.json() if response.status_code == 200 else []
|
||||
except Exception as e:
|
||||
print("Error loading clients:", e)
|
||||
|
||||
def create_list(self, items, on_click_handler):
|
||||
"""Helper to create list items for a column."""
|
||||
return [
|
||||
ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
ft.Text(item['name'], expand=True),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
width=300,
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
padding=10,
|
||||
border_radius=8,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
ink=True, # To enable click effect
|
||||
on_click=lambda e, id=item: on_click_handler(id), # Attach the click handler
|
||||
)
|
||||
for item in items
|
||||
]
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("Tenants", size=24, weight=ft.FontWeight.BOLD),
|
||||
|
||||
ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
self.search_field,
|
||||
ft.Button(
|
||||
"Search",
|
||||
icon=ft.Icons.SEARCH,
|
||||
width=100,
|
||||
on_click=self.on_search_btn_click
|
||||
)
|
||||
]
|
||||
),
|
||||
|
||||
]
|
||||
),
|
||||
|
||||
ft.Row(
|
||||
[
|
||||
|
||||
self.tenants_list,
|
||||
self.user_details
|
||||
],
|
||||
vertical_alignment=ft.CrossAxisAlignment.START
|
||||
)
|
||||
|
||||
],
|
||||
scroll=ft.ScrollMode.ADAPTIVE
|
||||
),
|
||||
expand=True
|
||||
)
|
||||
126
transportmanager/client/pages/archive_in_page.py
Normal file
126
transportmanager/client/pages/archive_in_page.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from pages.view_orders_in_page import ViewOrdersIn
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ArchiveInPage:
|
||||
def __init__(self, page: ft.Page, dashboard, orders_in):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.orders_in = orders_in
|
||||
self.orders = []
|
||||
self.orders_list = ft.Column(spacing=10, expand=True, scroll=ft.ScrollMode.ADAPTIVE,)
|
||||
|
||||
self.selected_delete_order = None
|
||||
|
||||
self.delete_dialog = ft.AlertDialog(
|
||||
title="Delete order?",
|
||||
actions=[
|
||||
ft.Button(
|
||||
"Yes",
|
||||
width=100,
|
||||
on_click=self.on_confirm_delete_order_click
|
||||
),
|
||||
ft.FilledButton(
|
||||
"No",
|
||||
bgcolor=ft.Colors.GREY,
|
||||
width = 100,
|
||||
on_click=self.on_no_delete_order_btn_click
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
def on_confirm_delete_order_click(self, e):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(f"{API_BASE_URL}/orders_in/{self.selected_delete_order['id']}", headers=headers)
|
||||
if response.status_code == 200:
|
||||
print('Order deleted!')
|
||||
except Exception as e:
|
||||
print("Error loading orders:", e)
|
||||
self.page.close(self.delete_dialog)
|
||||
self.selected_delete_order = None
|
||||
self.refresh()
|
||||
|
||||
def on_no_delete_order_btn_click(self, e):
|
||||
self.page.close(self.delete_dialog)
|
||||
self.selected_delete_order = None
|
||||
|
||||
def view_order(self, order):
|
||||
if order:
|
||||
self.page.session.set("order_in_id", order['id'])
|
||||
self.view_order_page = ViewOrdersIn(self.page, self, self.dashboard)
|
||||
self.dashboard.placeholder.content = self.view_order_page.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def cancel_order(self, order):
|
||||
self.selected_delete_order = order
|
||||
self.page.open(self.delete_dialog)
|
||||
|
||||
def load_orders(self):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/orders_in/list", headers=headers)
|
||||
return response.json() if response.status_code == 200 else []
|
||||
except Exception as e:
|
||||
print("Error loading orders:", e)
|
||||
|
||||
def on_go_back_btn_click(self, e):
|
||||
self.dashboard.placeholder.content = self.orders_in.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def get_client(self, client_id):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/clients/{client_id}", headers=headers)
|
||||
if response.status_code == 200:
|
||||
client = response.json()
|
||||
return client['name']
|
||||
else:
|
||||
return None
|
||||
except Exception as e:
|
||||
print("Error loading clients:", e)
|
||||
return None
|
||||
|
||||
def refresh(self):
|
||||
self.orders = self.load_orders()
|
||||
self.orders_list.controls.clear()
|
||||
for order in self.orders:
|
||||
client = self.get_client(order['client_id'])
|
||||
self.orders_list.controls.append(
|
||||
ft.Container(
|
||||
content=ft.Row([
|
||||
ft.Column([
|
||||
ft.Text(f"{client}", size=16, weight=ft.FontWeight.BOLD),
|
||||
ft.Text(f"Order Number: {order['order_number']}", size=14),
|
||||
], expand=True),
|
||||
ft.Button("View",icon=ft.Icons.PICTURE_AS_PDF, on_click=lambda e, o=order: self.view_order(o)),
|
||||
ft.Button("Delete", icon=ft.Icons.CANCEL, on_click=lambda e, o=order: self.cancel_order(o))
|
||||
]),
|
||||
padding=10,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
border_radius=10,
|
||||
)
|
||||
)
|
||||
self.page.update()
|
||||
|
||||
def build(self):
|
||||
self.refresh()
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Archive", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
),
|
||||
self.orders_list
|
||||
]
|
||||
)
|
||||
)
|
||||
125
transportmanager/client/pages/archive_page.py
Normal file
125
transportmanager/client/pages/archive_page.py
Normal file
@@ -0,0 +1,125 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from pages.view_page import ViewPage
|
||||
from pages.orders_edit_page import OrdersEditPage
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ArchivePage:
|
||||
def __init__(self, page:ft.Page, dashboard, order_page):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.orders = []
|
||||
self.orders_list = ft.Column(spacing=10, expand=True, scroll=ft.ScrollMode.ADAPTIVE,)
|
||||
self.order_page = order_page
|
||||
self.selected_cancel_order = None
|
||||
|
||||
self.cancel_dialog = ft.AlertDialog(
|
||||
title="Cancel order?",
|
||||
actions=[
|
||||
ft.Button(
|
||||
"Yes",
|
||||
width=100,
|
||||
on_click=self.on_confirm_calcel_order_click
|
||||
),
|
||||
ft.FilledButton(
|
||||
"No",
|
||||
bgcolor=ft.Colors.GREY,
|
||||
width = 100,
|
||||
on_click=self.on_no_cancel_order_btn_click
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
def refresh(self):
|
||||
self.orders = self.load_orders()
|
||||
self.orders_list.controls.clear()
|
||||
for order in self.orders:
|
||||
self.orders_list.controls.append(
|
||||
ft.Container(
|
||||
content=ft.Row([
|
||||
ft.Column([
|
||||
ft.Text(f"{order['order_number']}", size=16, weight=ft.FontWeight.BOLD),
|
||||
], expand=True),
|
||||
ft.Button("View",icon=ft.Icons.PICTURE_AS_PDF, on_click=lambda e, o=order: self.view_order(o)),
|
||||
*([
|
||||
ft.Button("Edit", icon=ft.Icons.EDIT, on_click=lambda e, o=order: self.edit_order(o)),
|
||||
ft.Button("Cancel", icon=ft.Icons.CANCEL, on_click=lambda e, o=order: self.cancel_order(o))
|
||||
] if order['status'] == 'active' else [])
|
||||
]),
|
||||
padding=10,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
border_radius=10,
|
||||
)
|
||||
)
|
||||
self.page.update()
|
||||
|
||||
def load_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 view_order(self, order):
|
||||
#print(order)
|
||||
user_id = self.page.session.get("user_id")
|
||||
pdf_name = f'order_{user_id}_{order['order_number']}.pdf'
|
||||
#print(pdf_name)
|
||||
view_page = ViewPage(self.page, pdf_name, self.order_page, self.dashboard, order['id'])
|
||||
self.dashboard.placeholder.content = view_page.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def edit_order(self, order):
|
||||
self.page.session.set('order_id', order['id'])
|
||||
edit_order = OrdersEditPage(self.page, self.dashboard, self)
|
||||
self.dashboard.placeholder.content = edit_order.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def cancel_order(self, order):
|
||||
self.selected_cancel_order = order
|
||||
self.page.open(self.cancel_dialog)
|
||||
|
||||
def on_confirm_calcel_order_click(self, e):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.delete(
|
||||
f"{API_BASE_URL}/orders/cancel/{self.selected_cancel_order['id']}",
|
||||
headers=headers
|
||||
)
|
||||
if response.status_code != 200:
|
||||
print("Failed to cancel order:", response.status_code)
|
||||
except Exception as e:
|
||||
print("Error cancelling order:", e)
|
||||
|
||||
self.page.close(self.cancel_dialog)
|
||||
self.selected_cancel_order = None
|
||||
self.refresh() # Optional: Refresh the list
|
||||
|
||||
def on_no_cancel_order_btn_click(self, e):
|
||||
self.selected_cancel_order = None
|
||||
self.page.close(self.cancel_dialog)
|
||||
|
||||
def on_go_back_btn_click(self, e):
|
||||
self.dashboard.placeholder.content = self.order_page.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def build(self):
|
||||
self.refresh()
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Archive", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
),
|
||||
self.orders_list
|
||||
]
|
||||
)
|
||||
)
|
||||
73
transportmanager/client/pages/auth_page.py
Normal file
73
transportmanager/client/pages/auth_page.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# client/pages/login_page.py
|
||||
import flet as ft
|
||||
from pages.login_page import Login
|
||||
|
||||
class Auth:
|
||||
def __init__(self, page: ft.Page):
|
||||
self.page = page
|
||||
self.email = ft.TextField(label="Email")
|
||||
self.passwd = ft.TextField(label="Password", password=True, can_reveal_password=True)
|
||||
self.error_message = ft.Text("")
|
||||
self.welcome_message = ft.Text(
|
||||
"",
|
||||
weight=ft.FontWeight.BOLD,
|
||||
size=50,
|
||||
color= ft.Colors.WHITE
|
||||
)
|
||||
|
||||
# if self.page.client_storage.get("logo_filename"):
|
||||
# logo_path = f'images/{self.page.client_storage.get("logo_filename")}'
|
||||
# else:
|
||||
# print(f'filename: {self.page.client_storage.get("logo_filename")}')
|
||||
logo_path = "images/truck_logo.png"
|
||||
|
||||
self.logo = ft.Image(
|
||||
src=logo_path,
|
||||
width=400,
|
||||
border_radius=20
|
||||
)
|
||||
self.subtitle = ft.Text(
|
||||
"From Order to Action, Instantly.",
|
||||
size=20,
|
||||
color=ft.Colors.WHITE70
|
||||
)
|
||||
login = Login(self.page, self)
|
||||
self.placeholder = ft.Container(
|
||||
content=login.build(),
|
||||
padding=10,
|
||||
border_radius=10,
|
||||
alignment=ft.alignment.center,
|
||||
expand=5
|
||||
)
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
ft.Container(
|
||||
content = ft.Column(
|
||||
[
|
||||
self.logo,
|
||||
self.subtitle
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
alignment=ft.MainAxisAlignment.CENTER
|
||||
),
|
||||
expand=5,
|
||||
gradient=ft.RadialGradient(
|
||||
center=ft.Alignment(-0.50, -0.50),
|
||||
radius=1.0,
|
||||
colors=[ft.Colors.BLUE_300, ft.Colors.BLUE_900],
|
||||
tile_mode=ft.GradientTileMode.CLAMP
|
||||
),
|
||||
shape=ft.BoxShape.CIRCLE,
|
||||
margin=ft.margin.only(left=-180, top=-180)
|
||||
),
|
||||
self.placeholder,
|
||||
]
|
||||
),
|
||||
alignment=ft.alignment.center,
|
||||
expand=True,
|
||||
padding=0,
|
||||
margin=0,
|
||||
)
|
||||
294
transportmanager/client/pages/clients_page.py
Normal file
294
transportmanager/client/pages/clients_page.py
Normal file
@@ -0,0 +1,294 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ClientsPage:
|
||||
def __init__(self, page: ft.Page, dashboard):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.clients = []
|
||||
self.dialog = None
|
||||
self.name = ft.TextField(
|
||||
label="Name",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.address = None
|
||||
self.street_and_number = ft.TextField(
|
||||
label="Street and number",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.postal_code = ft.TextField(
|
||||
label="Postal code",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.city = ft.TextField(
|
||||
label="City",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.region_county = ft.TextField(
|
||||
label="Region / County",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.country = ft.TextField(
|
||||
label="Country",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.register_number = ft.TextField(
|
||||
label="Register Number",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.vat = ft.TextField(
|
||||
label="VAT",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.contact_person = ft.TextField(
|
||||
label="Contact Person",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.phone = ft.TextField(
|
||||
label="Phone",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.email = ft.TextField(
|
||||
label="Email",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.selected_client_id = None
|
||||
self.clients_list = ft.ListView(expand=True, spacing=10)
|
||||
self.dialog = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Client Details"),
|
||||
content=ft.Column(
|
||||
controls=[
|
||||
self.name,
|
||||
self.register_number,
|
||||
self.vat,
|
||||
self.contact_person,
|
||||
self.phone,
|
||||
self.email,
|
||||
self.street_and_number,
|
||||
self.postal_code,
|
||||
self.city,
|
||||
self.region_county,
|
||||
self.country,
|
||||
],
|
||||
width=600
|
||||
),
|
||||
actions=[
|
||||
ft.TextButton("Cancel", on_click=self.on_cancel_btn_click),
|
||||
ft.ElevatedButton("Save", on_click=self.submit_client)
|
||||
]
|
||||
)
|
||||
self.confirm_dialog = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Delete Client"),
|
||||
content=ft.Text("Are you sure you want to delete this client?"),
|
||||
actions=[
|
||||
ft.TextButton("Cancel", on_click=lambda e: self.page.close(self.confirm_dialog)),
|
||||
ft.ElevatedButton("Delete", on_click=self.confirm_delete)
|
||||
]
|
||||
)
|
||||
self.client_id_to_delete = None
|
||||
self.subscription_error = ft.Text("Please subscribe to add new client", color=ft.Colors.RED)
|
||||
|
||||
def load_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)
|
||||
if response.status_code == 200:
|
||||
self.clients = response.json()
|
||||
self.refresh_clients_list()
|
||||
except Exception as e:
|
||||
print("Error loading clients:", e)
|
||||
|
||||
def refresh_clients_list(self):
|
||||
self.clients_list.controls.clear()
|
||||
for client in self.clients:
|
||||
self.clients_list.controls.append(
|
||||
ft.Container(
|
||||
content=ft.Row(
|
||||
[
|
||||
ft.Column([
|
||||
ft.Text(client["name"], size=16, weight=ft.FontWeight.BOLD),
|
||||
ft.Text(client["register_number"], size=12, italic=True)
|
||||
], expand=True),
|
||||
ft.Row([
|
||||
ft.IconButton(icon=ft.Icons.EDIT, on_click=lambda e, c=client: self.open_dialog(c)),
|
||||
ft.IconButton(icon=ft.Icons.DELETE, on_click=lambda e, cid=client["id"]: self.delete_client(cid)),
|
||||
])
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
),
|
||||
padding=10,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
border_radius=10,
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
expand=True
|
||||
)
|
||||
)
|
||||
self.page.update()
|
||||
|
||||
def open_dialog(self, client=None):
|
||||
if client:
|
||||
self.selected_client_id = client["id"]
|
||||
self.name.value = client["name"]
|
||||
self.street_and_number.value = client["address"].split(" %")[0]
|
||||
self.postal_code.value = client["address"].split(" %")[1]
|
||||
self.city.value = client["address"].split(" %")[2]
|
||||
self.region_county.value = client["address"].split(" %")[3]
|
||||
self.country.value = client["address"].split(" %")[4]
|
||||
self.register_number.value = client["register_number"]
|
||||
self.contact_person.value = client["contact_person"]
|
||||
self.phone.value = client["phone"]
|
||||
self.email.value = client["email"]
|
||||
self.vat.value = client['vat']
|
||||
else:
|
||||
self.selected_client_id = None
|
||||
self.name.value = ""
|
||||
self.street_and_number.value = ""
|
||||
self.postal_code.value = ""
|
||||
self.city.value = ""
|
||||
self.region_county.value = ""
|
||||
self.country.value = ""
|
||||
self.register_number.value = ""
|
||||
self.contact_person.value = ""
|
||||
self.phone.value = ""
|
||||
self.email.value = ""
|
||||
self.vat.value = ""
|
||||
|
||||
self.page.open(self.dialog)
|
||||
self.page.update()
|
||||
|
||||
def submit_client(self, e):
|
||||
user_id = self.page.session.get("user_id")
|
||||
address = f'{self.street_and_number.value} %{self.postal_code.value} %{self.city.value} %{self.region_county.value} %{self.country.value}'
|
||||
|
||||
client_data = {
|
||||
"name": self.name.value,
|
||||
"address": address,
|
||||
"register_number": self.register_number.value,
|
||||
"vat": self.vat.value,
|
||||
"contact_person": self.contact_person.value,
|
||||
"phone": self.phone.value,
|
||||
"email": self.email.value,
|
||||
"user_id": user_id,
|
||||
}
|
||||
try:
|
||||
headers = {"Authorization": f"Bearer {self.page.client_storage.get('token')}"}
|
||||
if self.selected_client_id:
|
||||
requests.put(f"{API_BASE_URL}/clients/{self.selected_client_id}", json=client_data, headers=headers)
|
||||
else:
|
||||
requests.post(f"{API_BASE_URL}/clients/", json=client_data, headers=headers)
|
||||
self.page.close(self.dialog)
|
||||
self.load_clients()
|
||||
self.page.snack_bar = ft.SnackBar(ft.Text("Client saved successfully."))
|
||||
self.page.snack_bar.open = True
|
||||
self.page.update()
|
||||
except Exception as e:
|
||||
print("Error submitting client:", e)
|
||||
|
||||
def on_cancel_btn_click(self, e):
|
||||
self.page.close(self.dialog)
|
||||
|
||||
def delete_client(self, client_id):
|
||||
self.client_id_to_delete = client_id
|
||||
self.page.open(self.confirm_dialog)
|
||||
self.page.update()
|
||||
|
||||
def confirm_delete(self, e):
|
||||
try:
|
||||
headers = {"Authorization": f"Bearer {self.page.client_storage.get('token')}"}
|
||||
requests.delete(f"{API_BASE_URL}/clients/{self.client_id_to_delete}", headers=headers)
|
||||
self.page.close(self.confirm_dialog)
|
||||
self.load_clients()
|
||||
except Exception as ex:
|
||||
print("Error deleting client:", ex)
|
||||
|
||||
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.load_clients()
|
||||
self.subscription = self.get_current_subscription_plan()
|
||||
self.add_client_btn = ft.ElevatedButton("New Client", icon=ft.Icons.ADD, on_click=lambda e: self.open_dialog())
|
||||
self.header = ft.Row(
|
||||
[
|
||||
ft.Text("Clients", size=24, weight=ft.FontWeight.BOLD),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
)
|
||||
if self.subscription:
|
||||
if self.subscription['status'] != 'expired':
|
||||
self.header.controls.append(self.add_client_btn)
|
||||
else:
|
||||
self.header.controls.append(self.subscription_error)
|
||||
else:
|
||||
self.header.controls.append(self.subscription_error)
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
self.header,
|
||||
self.clients_list
|
||||
],
|
||||
expand=True
|
||||
),
|
||||
expand=True,
|
||||
)
|
||||
495
transportmanager/client/pages/dashboard_page.py
Normal file
495
transportmanager/client/pages/dashboard_page.py
Normal file
@@ -0,0 +1,495 @@
|
||||
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.report_page import ReportPage
|
||||
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:
|
||||
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 = ReportPage(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
|
||||
)
|
||||
|
||||
|
||||
233
transportmanager/client/pages/destinations_page.py
Normal file
233
transportmanager/client/pages/destinations_page.py
Normal file
@@ -0,0 +1,233 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class DestinationsPage:
|
||||
def __init__(self, page: ft.Page, dashboard):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.destinations = []
|
||||
self.destinations_column = ft.Column(expand=True, spacing=10, scroll=ft.ScrollMode.ADAPTIVE,)
|
||||
self.dialog = None
|
||||
self.delete_alert = None
|
||||
self.current_edit = None
|
||||
self.subscription_error = ft.Text("Please subscribe to add new destination", color=ft.Colors.RED)
|
||||
|
||||
def refresh(self):
|
||||
self.destinations_column.controls.clear()
|
||||
token = self.page.client_storage.get("token")
|
||||
if not token:
|
||||
print("Missing token.")
|
||||
return
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/destinations/", headers=headers)
|
||||
if response.status_code == 200:
|
||||
self.destinations = response.json()
|
||||
for destination in self.destinations:
|
||||
street_and_number = destination["address"].split(" %")[0]
|
||||
postal_code = destination["address"].split(" %")[1]
|
||||
city = destination["address"].split(" %")[2]
|
||||
region_county = destination["address"].split(" %")[3]
|
||||
country = destination["address"].split(" %")[4]
|
||||
address = ''
|
||||
if len(street_and_number) > 0:
|
||||
address += street_and_number +', '
|
||||
if len(postal_code) > 0:
|
||||
address += postal_code +', '
|
||||
if len(city) > 0:
|
||||
address += city +', '
|
||||
if len(region_county) > 0:
|
||||
address += region_county +', '
|
||||
if len(country) > 0:
|
||||
address += country
|
||||
self.destinations_column.controls.append(
|
||||
ft.Container(
|
||||
content=ft.Row([
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text(destination["name"], weight = ft.FontWeight.BOLD),
|
||||
ft.Text(address)
|
||||
]
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.IconButton(icon=ft.Icons.LOCATION_PIN, on_click= lambda e, d=destination: self.open_location(d)),
|
||||
ft.IconButton(icon=ft.Icons.EDIT, on_click=lambda e, d=destination: self.open_dialog(d)),
|
||||
ft.IconButton(icon=ft.Icons.DELETE, on_click=lambda e, d=destination: self.confirm_delete(d)),
|
||||
]
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
|
||||
padding=10,
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
border_radius=10,
|
||||
)
|
||||
)
|
||||
self.page.update()
|
||||
|
||||
def open_dialog(self, destination=None):
|
||||
self.current_edit = destination
|
||||
name = ft.TextField(
|
||||
label="Name",
|
||||
value=destination["name"] if destination else "",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
street_and_number = ft.TextField(
|
||||
label="Street and number",
|
||||
value=destination["address"].split("%")[0] if destination else "",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
postal_code = ft.TextField(
|
||||
label="Postal code",
|
||||
value=destination["address"].split("%")[1] if destination else "",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
city = ft.TextField(
|
||||
label="City",
|
||||
value=destination["address"].split("%")[2] if destination else "",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
region_county = ft.TextField(
|
||||
label="Region / County",
|
||||
value=destination["address"].split("%")[3] if destination else "",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
country = ft.TextField(
|
||||
label="Country",
|
||||
value=destination["address"].split("%")[4] if destination else "",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
|
||||
def save_action(e):
|
||||
address = f'{street_and_number.value} %{postal_code.value} %{city.value} %{region_county.value} %{country.value}'
|
||||
self.save_destination(name.value, address)
|
||||
self.dialog = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Edit Address" if destination else "Add Address"),
|
||||
content=ft.Column(
|
||||
[
|
||||
name,
|
||||
street_and_number,
|
||||
postal_code,
|
||||
city,
|
||||
region_county,
|
||||
country
|
||||
],
|
||||
width=400,
|
||||
height=400
|
||||
),
|
||||
actions=[
|
||||
ft.TextButton("Cancel", on_click=lambda e: self.page.close(self.dialog)),
|
||||
ft.ElevatedButton(
|
||||
"Save",
|
||||
on_click=save_action
|
||||
)
|
||||
],
|
||||
)
|
||||
self.page.dialog = self.dialog
|
||||
self.page.open(self.dialog)
|
||||
|
||||
def save_destination(self, name, address):
|
||||
token = self.page.client_storage.get("token")
|
||||
user_id = self.page.session.get("user_id")
|
||||
if not token or not user_id:
|
||||
print("Missing token or user_id.")
|
||||
return
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
data = {"name": name, "address": address, "user_id": user_id}
|
||||
if self.current_edit:
|
||||
url = f"{API_BASE_URL}/destinations/{self.current_edit['id']}"
|
||||
requests.put(url, json=data, headers=headers)
|
||||
else:
|
||||
requests.post(f"{API_BASE_URL}/destinations/", json=data, headers=headers)
|
||||
self.page.close(self.dialog)
|
||||
self.refresh()
|
||||
|
||||
def confirm_delete(self, destination):
|
||||
def delete_action(e):
|
||||
self.delete_destination(destination["id"])
|
||||
self.delete_alert = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Confirm Deletion"),
|
||||
content=ft.Text(f"Are you sure you want to delete destination: {destination['name']}?"),
|
||||
actions=[
|
||||
ft.TextButton("Cancel", on_click=lambda e: self.page.close(self.delete_alert)),
|
||||
ft.TextButton("Delete", on_click=delete_action)
|
||||
]
|
||||
)
|
||||
self.page.dialog = self.delete_alert
|
||||
self.page.open(self.delete_alert)
|
||||
|
||||
def delete_destination(self, id):
|
||||
token = self.page.client_storage.get("token")
|
||||
if not token:
|
||||
print("Missing token.")
|
||||
return
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
requests.delete(f"{API_BASE_URL}/destinations/{id}", headers=headers)
|
||||
self.page.close(self.delete_alert)
|
||||
self.refresh()
|
||||
|
||||
def open_location(self, destination):
|
||||
query = destination["address"].replace(" ", "+").replace(" %", "").replace(",", "")
|
||||
maps_url = f"https://www.google.com/maps/search/?api=1&query={query}"
|
||||
self.page.launch_url(maps_url)
|
||||
|
||||
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.refresh()
|
||||
self.add_destination_btn = ft.ElevatedButton("Add Destination", icon=ft.Icons.ADD, on_click=lambda e: self.open_dialog())
|
||||
self.headers = ft.Row([
|
||||
ft.Text("Adderess", size=24, weight=ft.FontWeight.BOLD),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
)
|
||||
subscription = self.get_current_subscription_plan()
|
||||
if subscription:
|
||||
if subscription['status'] != 'expired':
|
||||
self.headers.controls.append(self.add_destination_btn)
|
||||
else:
|
||||
self.headers.controls.append(self.subscription_error)
|
||||
else:
|
||||
self.headers.controls.append(self.subscription_error)
|
||||
return ft.Column(
|
||||
[
|
||||
self.headers,
|
||||
self.destinations_column
|
||||
],
|
||||
expand=True,
|
||||
)
|
||||
62
transportmanager/client/pages/forgot_password_page.py
Normal file
62
transportmanager/client/pages/forgot_password_page.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import flet as ft
|
||||
import re
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ForgotPassword:
|
||||
def __init__(self, page: ft.Page, auth, login):
|
||||
self.page = page
|
||||
self.auth = auth
|
||||
self.login = login
|
||||
self.email = ft.TextField(label="Email")
|
||||
self.error = ft.Text("", color=ft.Colors.RED)
|
||||
self.success = ft.Text("", color=ft.Colors.GREEN)
|
||||
self.back_button = ft.TextButton(text="Back to Login", on_click=self.on_back_clicked)
|
||||
|
||||
def on_submit(self, e):
|
||||
email_value = self.email.value.strip()
|
||||
email_regex = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+$"
|
||||
|
||||
if not re.match(email_regex, email_value):
|
||||
self.error.value = "Please enter a valid email address."
|
||||
self.success.value = ""
|
||||
else:
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/auth/forgot_password",
|
||||
json={"email": email_value},
|
||||
timeout=10
|
||||
)
|
||||
if response.status_code == 200:
|
||||
self.success.value = "If the email exists, you will receive a reset link shortly."
|
||||
self.error.value = ""
|
||||
else:
|
||||
self.error.value = "Server error. Please try again later."
|
||||
self.success.value = ""
|
||||
except Exception as ex:
|
||||
self.error.value = "Connection error. Please check your internet."
|
||||
self.success.value = ""
|
||||
print(ex)
|
||||
|
||||
self.page.update()
|
||||
|
||||
def on_back_clicked(self, e):
|
||||
self.auth.placeholder.content.clean()
|
||||
self.auth.placeholder.content = self.login.build()
|
||||
self.auth.placeholder.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Column(
|
||||
[
|
||||
ft.Text("Forgot Password", size=30, weight="bold"),
|
||||
self.email,
|
||||
ft.ElevatedButton("Reset Password", on_click=self.on_submit, width=150),
|
||||
self.error,
|
||||
self.success,
|
||||
self.back_button,
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
spacing=20,
|
||||
width=350
|
||||
)
|
||||
105
transportmanager/client/pages/login_page.py
Normal file
105
transportmanager/client/pages/login_page.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import flet as ft
|
||||
from pages.register_page import Register
|
||||
from pages.forgot_password_page import ForgotPassword
|
||||
from pages.two_factor_page import TwoFactorAuth
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class Login:
|
||||
def __init__(self, page: ft.Page, auth):
|
||||
self.page = page
|
||||
self.auth = auth
|
||||
self.email = ft.TextField(label="Email")
|
||||
self.passwd = ft.TextField(label="Password", password=True, can_reveal_password=True)
|
||||
self.error_message = ft.Text("", color=ft.Colors.RED)
|
||||
|
||||
def on_login_click(self, e):
|
||||
self.error_message.value = ""
|
||||
self.error_message.update()
|
||||
self.page.update()
|
||||
|
||||
email = self.email.value.strip()
|
||||
password = self.passwd.value.strip()
|
||||
|
||||
if not email or not password:
|
||||
self.error_message.value = "Please enter both email and password."
|
||||
self.error_message.update()
|
||||
return
|
||||
|
||||
# self.auth.placeholder.content.clean()
|
||||
# two_factor = TwoFactorAuth(self.page, email, self, self.auth)
|
||||
# self.auth.placeholder.content = two_factor.build()
|
||||
# self.auth.placeholder.update()
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/auth/login",
|
||||
json={"email": email, "password": password},
|
||||
timeout=10
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
user_info = requests.get(
|
||||
f"{API_BASE_URL}/auth/me",
|
||||
headers={"Authorization": f"Bearer {response.json().get('access_token', '')}"}
|
||||
)
|
||||
if user_info.status_code == 200:
|
||||
logo_filename = user_info.json().get("logo_filename", "")
|
||||
if logo_filename:
|
||||
self.page.client_storage.set("custom_logo", logo_filename)
|
||||
|
||||
self.auth.placeholder.content.clean()
|
||||
two_factor = TwoFactorAuth(self.page, email, self, self.auth)
|
||||
self.auth.placeholder.content = two_factor.build()
|
||||
self.auth.placeholder.update()
|
||||
|
||||
else:
|
||||
self.error_message.value = response.json().get("error", "Login failed")
|
||||
self.error_message.update()
|
||||
|
||||
except Exception as ex:
|
||||
self.error_message.value = f"Login error: {str(ex)}"
|
||||
self.error_message.update()
|
||||
|
||||
def on_forgot_btn_click(self, e):
|
||||
self.auth.placeholder.content.clean()
|
||||
forgot_passwd = ForgotPassword(self.page, self.auth, self)
|
||||
self.auth.placeholder.content = forgot_passwd.build()
|
||||
self.auth.placeholder.update()
|
||||
|
||||
def on_register_btn_click(self, e):
|
||||
print('Go to register')
|
||||
register = Register(self.page, self.auth, self)
|
||||
self.auth.placeholder.content.clean()
|
||||
self.auth.placeholder.content = register.build()
|
||||
self.auth.placeholder.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Column(
|
||||
[
|
||||
ft.Text(
|
||||
"Login",
|
||||
size=30,
|
||||
weight="bold"
|
||||
),
|
||||
self.email,
|
||||
self.passwd,
|
||||
ft.ElevatedButton(
|
||||
"Login",
|
||||
width = 150,
|
||||
on_click=self.on_login_click
|
||||
),
|
||||
self.error_message,
|
||||
ft.Row(
|
||||
[
|
||||
ft.TextButton("Forgot Password?", on_click=self.on_forgot_btn_click),
|
||||
ft.TextButton("Register", on_click=self.on_register_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
)
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
spacing=20,
|
||||
width=350
|
||||
)
|
||||
1375
transportmanager/client/pages/orders_edit_page.py
Normal file
1375
transportmanager/client/pages/orders_edit_page.py
Normal file
File diff suppressed because it is too large
Load Diff
1105
transportmanager/client/pages/orders_in_page.py
Normal file
1105
transportmanager/client/pages/orders_in_page.py
Normal file
File diff suppressed because it is too large
Load Diff
1229
transportmanager/client/pages/orders_out_page.py
Normal file
1229
transportmanager/client/pages/orders_out_page.py
Normal file
File diff suppressed because it is too large
Load Diff
99
transportmanager/client/pages/orders_page.py
Normal file
99
transportmanager/client/pages/orders_page.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import flet as ft
|
||||
from pages.orders_in_page import OrdersInPage
|
||||
from pages.orders_out_page import OrdersOutPage
|
||||
|
||||
class OrdersPage:
|
||||
def __init__(self, page: ft.Page, dashboard):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
|
||||
def on_orders_in_btn_click(self, e):
|
||||
order_in_page = OrdersInPage(self.page, self.dashboard)
|
||||
self.dashboard.placeholder.content = order_in_page.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def on_orders_out_btn_click(self, e):
|
||||
orders_out_page = OrdersOutPage(self.page, self.dashboard)
|
||||
self.dashboard.placeholder.content = orders_out_page.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("Orders", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Icon(ft.Icons.ARROW_CIRCLE_DOWN, size=150),
|
||||
ft.Container(
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Incoming orders", size=20)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER
|
||||
),
|
||||
bgcolor=ft.Colors.BLUE_200,
|
||||
width=250,
|
||||
height=80
|
||||
),
|
||||
ft.FilledButton(
|
||||
"Orders In",
|
||||
on_click=self.on_orders_in_btn_click,
|
||||
width=150
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
),
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
padding = ft.padding.symmetric(vertical=20),
|
||||
width=250,
|
||||
height=350,
|
||||
border_radius=20
|
||||
),
|
||||
ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Icon(ft.Icons.ARROW_CIRCLE_UP, size=150),
|
||||
ft.Container(
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Outcoming orders", size=20)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER
|
||||
),
|
||||
bgcolor=ft.Colors.BLUE_200,
|
||||
width=250,
|
||||
height=80
|
||||
),
|
||||
ft.FilledButton(
|
||||
"Orders Out",
|
||||
on_click=self.on_orders_out_btn_click,
|
||||
width=150
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
),
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
padding = ft.padding.symmetric(vertical=20),
|
||||
width=250,
|
||||
height=350,
|
||||
border_radius=20
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
spacing=50
|
||||
),
|
||||
ft.Text(" ")
|
||||
],
|
||||
expand=True,
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
|
||||
),
|
||||
expand=True
|
||||
)
|
||||
458
transportmanager/client/pages/profile_page.py
Normal file
458
transportmanager/client/pages/profile_page.py
Normal file
@@ -0,0 +1,458 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import time
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ProfilePage:
|
||||
def __init__(self, page: ft.Page, dashboard):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.name_field = ft.TextField(
|
||||
label="Company Name",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.contact_name_field = ft.TextField(
|
||||
label="Contact Name",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.email_field = ft.TextField(
|
||||
label="Email",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.phone_field = ft.TextField(
|
||||
label="Phone",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.register_number_field = ft.TextField(
|
||||
label="Register Number",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.vat = ft.TextField(
|
||||
label="VAT",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.address = None
|
||||
self.street_and_number = ft.TextField(
|
||||
label="Street and number",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.postal_code = ft.TextField(
|
||||
label="Postal code",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.city = ft.TextField(
|
||||
label="City",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.region_county = ft.TextField(
|
||||
label="Region / County",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.country = ft.TextField(
|
||||
label="Country",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.logo_field = ft.TextField(
|
||||
label="Logo Filename",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.terms_field = ft.TextField(
|
||||
label="Terms",
|
||||
multiline=True,
|
||||
min_lines=5,
|
||||
max_lines=10,
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.first_order_number_field = ft.TextField(
|
||||
label="First Order Number",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.created_at_text = ft.Text(value="Created At: TBD") # Set dynamically later
|
||||
self.edit_button = ft.ElevatedButton(text="Edit Profile", on_click=self.on_edit_click)
|
||||
self.save_button = ft.ElevatedButton(text="Save Changes", visible=False, on_click=self.on_save_click)
|
||||
self.cancel_button = ft.TextButton(text="Cancel", visible=False, on_click=self.on_cancel_click)
|
||||
self.upload_logo_btn = ft.ElevatedButton("Upload Company Logo", icon=ft.Icons.UPLOAD, on_click=self.on_upload_click, disabled=True)
|
||||
self.message = ft.Text()
|
||||
self.logo = ft.Image(src="images/image_placeholder.png", width=250)
|
||||
self.file_picker = ft.FilePicker(on_result=self.on_file_result)
|
||||
self.page.overlay.append(self.file_picker)
|
||||
|
||||
#email credentials
|
||||
self.email_credentials = None
|
||||
self.smtp_host = ft.TextField(
|
||||
label="SMTP HOST",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.smtp_port = ft.TextField(
|
||||
label="SMTP PORT",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.smtp_user = ft.TextField(
|
||||
label="Email (SMTP USER)",
|
||||
disabled=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
|
||||
def _auth_headers(self):
|
||||
"""Build Authorization header from client storage robustly (web/desktop)."""
|
||||
t = self.page.client_storage.get("token")
|
||||
if not t:
|
||||
return {}
|
||||
# Token may be stored as dict or quoted string depending on login flow/runtime
|
||||
if isinstance(t, dict):
|
||||
t = t.get("access_token") or t.get("token") or ""
|
||||
if not isinstance(t, str):
|
||||
t = str(t)
|
||||
t = t.strip().strip('"')
|
||||
return {"Authorization": f"Bearer {t}"} if t else {}
|
||||
|
||||
def on_edit_click(self, e):
|
||||
self.name_field.disabled = False
|
||||
self.contact_name_field.disabled = False
|
||||
self.email_field.disabled = False
|
||||
self.phone_field.disabled = False
|
||||
self.register_number_field.disabled = False
|
||||
self.vat.disabled = False
|
||||
self.street_and_number.disabled = False
|
||||
self.postal_code.disabled = False
|
||||
self.city.disabled = False
|
||||
self.region_county.disabled = False
|
||||
self.country.disabled = False
|
||||
self.logo_field.disabled = False
|
||||
self.terms_field.disabled = False
|
||||
self.first_order_number_field.disabled = False
|
||||
self.upload_logo_btn.disabled = False
|
||||
self.smtp_host.disabled = False
|
||||
self.smtp_port.disabled = False
|
||||
self.smtp_user.disabled = False
|
||||
self.edit_button.visible = False
|
||||
self.save_button.visible = True
|
||||
self.cancel_button.visible = True
|
||||
self.page.update()
|
||||
|
||||
def on_save_click(self, e):
|
||||
# Save logic would be implemented here
|
||||
headers = self._auth_headers()
|
||||
if not headers:
|
||||
self.message.value = "Unauthorized: No token"
|
||||
self.message.color = ft.Colors.RED
|
||||
self.page.update()
|
||||
return
|
||||
address = f'{self.street_and_number.value} %{self.postal_code.value} %{self.city.value} %{self.region_county.value} %{self.country.value}'
|
||||
data = {
|
||||
"name": self.name_field.value,
|
||||
"contact_name": self.contact_name_field.value,
|
||||
"email": self.email_field.value,
|
||||
"phone": self.phone_field.value,
|
||||
"register_number": self.register_number_field.value,
|
||||
"vat": self.vat.value,
|
||||
"address": address,
|
||||
"logo_filename": self.logo_field.value,
|
||||
"terms": self.terms_field.value,
|
||||
"first_order_number": self.first_order_number_field.value
|
||||
}
|
||||
try:
|
||||
response = requests.put(f"{API_BASE_URL}/profile/", json=data, headers=headers)
|
||||
if response.status_code == 200:
|
||||
self.message.value = "Profile updated successfully!"
|
||||
self.message.color = ft.Colors.GREEN
|
||||
# Save logo filename to client storage
|
||||
self.page.client_storage.set("logo_filename", self.logo_field.value)
|
||||
self.page.session.set("first_order_number", self.first_order_number_field.value)
|
||||
else:
|
||||
self.message.value = f"Failed to update profile: {response.status_code}"
|
||||
self.message.color = ft.Colors.RED
|
||||
except Exception as e:
|
||||
self.message.value = f"Error updating profile: {e}"
|
||||
self.message.color = ft.Colors.RED
|
||||
|
||||
#email credentials
|
||||
data = {
|
||||
'smtp_host' : self.smtp_host.value,
|
||||
'smtp_port' : self.smtp_port.value,
|
||||
'smtp_user' : self.smtp_user.value
|
||||
}
|
||||
try:
|
||||
if not self.email_credentials:
|
||||
response = requests.post(f"{API_BASE_URL}/profile/email", json=data, headers=headers)
|
||||
else:
|
||||
response = requests.put(f"{API_BASE_URL}/profile/email", json=data, headers=headers)
|
||||
if response.status_code == 200:
|
||||
self.message.value = "Profile updated successfully!"
|
||||
self.message.color = ft.Colors.GREEN
|
||||
else:
|
||||
self.message.value = f"Failed to create / update email credentials: {response.status_code}"
|
||||
self.message.color = ft.Colors.RED
|
||||
except Exception as e:
|
||||
self.message.value = f"Error creating / updating email credentials: {e}"
|
||||
self.message.color = ft.Colors.RED
|
||||
|
||||
self.name_field.disabled = True
|
||||
self.contact_name_field.disabled = True
|
||||
self.email_field.disabled = True
|
||||
self.phone_field.disabled = True
|
||||
self.register_number_field.disabled = True
|
||||
self.vat.disabled = True
|
||||
self.street_and_number.disabled = True
|
||||
self.postal_code.disabled = True
|
||||
self.city.disabled = True
|
||||
self.region_county.disabled = True
|
||||
self.country.disabled = True
|
||||
self.logo_field.disabled = True
|
||||
self.terms_field.disabled = True
|
||||
self.first_order_number_field.disabled = True
|
||||
self.upload_logo_btn.disabled = True
|
||||
self.smtp_host.disabled = True
|
||||
self.smtp_port.disabled = True
|
||||
self.smtp_user.disabled = True
|
||||
self.edit_button.visible = True
|
||||
self.save_button.visible = False
|
||||
self.cancel_button.visible = False
|
||||
self.page.update()
|
||||
|
||||
def on_cancel_click(self, e):
|
||||
# Reset fields or fetch previous values here
|
||||
self.name_field.disabled = True
|
||||
self.contact_name_field.disabled = True
|
||||
self.email_field.disabled = True
|
||||
self.phone_field.disabled = True
|
||||
self.register_number_field.disabled = True
|
||||
self.vat.disabled = True
|
||||
self.street_and_number.disabled = True
|
||||
self.postal_code.disabled = True
|
||||
self.city.disabled = True
|
||||
self.region_county.disabled = True
|
||||
self.country.disabled = True
|
||||
self.logo_field.disabled = True
|
||||
self.terms_field.disabled = True
|
||||
self.first_order_number_field.disabled = True
|
||||
self.upload_logo_btn.disabled = True
|
||||
self.smtp_host.disabled = True
|
||||
self.smtp_port.disabled = True
|
||||
self.smtp_user.disabled = True
|
||||
self.edit_button.visible = True
|
||||
self.save_button.visible = False
|
||||
self.cancel_button.visible = False
|
||||
self.page.update()
|
||||
|
||||
def populate_user_data(self):
|
||||
user_id = self.page.session.get("user_id")
|
||||
if not user_id:
|
||||
self.message.value = "User not authenticated."
|
||||
self.message.color = ft.Colors.RED
|
||||
return
|
||||
headers = self._auth_headers()
|
||||
if not headers:
|
||||
self.message.value = "Unauthorized: No token"
|
||||
self.message.color = ft.Colors.RED
|
||||
return
|
||||
response = requests.get(f"{API_BASE_URL}/profile/", headers=headers, timeout=10)
|
||||
if response.status_code == 200:
|
||||
user_data = response.json()
|
||||
#print(user_data)
|
||||
self.name_field.value = user_data.get("name", "")
|
||||
self.contact_name_field.value = user_data.get("contact_name", "")
|
||||
self.email_field.value = user_data.get("email", "")
|
||||
self.phone_field.value = user_data.get("phone", "")
|
||||
self.register_number_field.value = user_data.get("register_number", "")
|
||||
self.vat.value = user_data.get("vat", "")
|
||||
self.street_and_number.value = user_data.get("address").split(" %")[0] if user_data.get("address") and len(user_data.get("address")) > 0 else ''
|
||||
self.postal_code.value = user_data.get("address").split(" %")[1] if user_data.get("address") and len(user_data.get("address")) > 0 else ''
|
||||
self.city.value = user_data.get("address").split(" %")[2] if user_data.get("address") and len(user_data.get("address")) > 0 else ''
|
||||
self.region_county.value = user_data.get("address").split(" %")[3] if user_data.get("address") and len(user_data.get("address")) > 0 else ''
|
||||
self.country.value = user_data.get("address").split(" %")[4] if user_data.get("address") and len(user_data.get("address")) > 0 else ''
|
||||
logo_filename = user_data.get("logo_filename", "")
|
||||
self.logo_field.value = logo_filename
|
||||
self.logo.src = f"images/{logo_filename}" if logo_filename else "images/image_placeholder.png"
|
||||
if logo_filename:
|
||||
self.page.client_storage.set("logo_filename", logo_filename)
|
||||
self.dashboard.logo.src = f"images/{logo_filename}"
|
||||
self.dashboard.logo.update()
|
||||
self.terms_field.value = user_data.get("terms", "")
|
||||
self.first_order_number_field.value = user_data.get("first_order_number", "")
|
||||
self.created_at_text.value = f"Created At: {user_data.get('created_at', '')}"
|
||||
if self.register_number_field.value == '':
|
||||
self.on_edit_click('')
|
||||
else:
|
||||
self.message.value = f"Failed to load user data: {response.text}"
|
||||
self.message.color = ft.Colors.RED
|
||||
|
||||
response = requests.get(f"{API_BASE_URL}/profile/email", headers=headers, timeout=10)
|
||||
if response.status_code == 200:
|
||||
self.email_credentials = response.json()
|
||||
self.smtp_host.value = self.email_credentials['smtp_host']
|
||||
self.smtp_port.value = self.email_credentials['smtp_port']
|
||||
self.smtp_user.value = self.email_credentials['smtp_user']
|
||||
else:
|
||||
print(f"Failed to load email credentials: {response.text}")
|
||||
|
||||
#except Exception as e:
|
||||
# self.message.value = f"Error fetching user data: {e}"
|
||||
# self.message.color = ft.Colors.RED
|
||||
|
||||
def on_upload_click(self, e):
|
||||
self.file_picker.pick_files(
|
||||
allow_multiple=False,
|
||||
allowed_extensions=["png", "jpg", "jpeg"]
|
||||
)
|
||||
|
||||
def on_file_result(self, e: ft.FilePickerResultEvent):
|
||||
if not e.files:
|
||||
return
|
||||
file = e.files[0]
|
||||
new_filename = f"user_logo_{self.page.session.get('user_id')}_{file.name}"
|
||||
upload_url = self.page.get_upload_url(new_filename, 1000)
|
||||
self.file_picker.upload([ft.FilePickerUploadFile(file.name, upload_url=upload_url)])
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
source_path = os.path.join("uploads", new_filename)
|
||||
destination_path = os.path.join("assets/images", new_filename)
|
||||
try:
|
||||
time.sleep(2)
|
||||
shutil.move(source_path, destination_path)
|
||||
self.logo.src = f"images/{new_filename}"
|
||||
self.logo.update()
|
||||
self.logo_field.value = new_filename
|
||||
self.page.update()
|
||||
self.page.client_storage.set("logo_filename", new_filename)
|
||||
self.dashboard.logo.src = f"images/{new_filename}"
|
||||
self.dashboard.logo.update()
|
||||
except Exception as err:
|
||||
self.message.value = f"Upload error: {err}"
|
||||
self.message.color = ft.Colors.RED
|
||||
self.page.update()
|
||||
|
||||
def build(self):
|
||||
self.populate_user_data()
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("User Profile", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Column(
|
||||
[
|
||||
self.logo,
|
||||
self.upload_logo_btn,
|
||||
self.message,
|
||||
ft.Text(),
|
||||
self.smtp_user,
|
||||
self.smtp_host,
|
||||
self.smtp_port,
|
||||
ft.Row([self.edit_button, self.save_button, self.cancel_button])
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
width=250
|
||||
),
|
||||
ft.Column(
|
||||
[
|
||||
self.name_field,
|
||||
self.contact_name_field,
|
||||
self.email_field,
|
||||
self.phone_field,
|
||||
self.register_number_field,
|
||||
self.vat,
|
||||
self.street_and_number,
|
||||
self.postal_code,
|
||||
self.city,
|
||||
self.region_county,
|
||||
self.country,
|
||||
self.first_order_number_field,
|
||||
self.terms_field,
|
||||
],
|
||||
spacing=20,
|
||||
expand=True
|
||||
)
|
||||
],
|
||||
vertical_alignment=ft.CrossAxisAlignment.START
|
||||
)
|
||||
],
|
||||
scroll=ft.ScrollMode.ADAPTIVE
|
||||
),
|
||||
)
|
||||
83
transportmanager/client/pages/register_page.py
Normal file
83
transportmanager/client/pages/register_page.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import re
|
||||
import flet as ft
|
||||
import requests
|
||||
import time
|
||||
from config import API_BASE_URL
|
||||
|
||||
class Register:
|
||||
def __init__(self, page: ft.Page, auth, login):
|
||||
self.page = page
|
||||
self.auth = auth
|
||||
self.login = login
|
||||
self.email = ft.TextField(label="Email", width=300)
|
||||
self.password = ft.TextField(label="Password", password=True, can_reveal_password=True, width=300)
|
||||
self.confirm_password = ft.TextField(label="Confirm Password", password=True, can_reveal_password=True, width=300)
|
||||
self.name = ft.TextField(label="Company Name", width=300)
|
||||
self.register_button = ft.ElevatedButton(text="Register", on_click=self.on_register_clicked, width=150)
|
||||
self.back_button = ft.TextButton(text="Back to Login", on_click=self.on_back_clicked)
|
||||
self.message = ft.Text(value="", color=ft.Colors.RED)
|
||||
|
||||
def on_register_clicked(self, e):
|
||||
# Email validation
|
||||
email_regex = r"^[^@]+@[^@]+\.[^@\.]+(\.[^@\.]+)*$"
|
||||
if not re.match(email_regex, self.email.value):
|
||||
self.message.value = "Invalid email address"
|
||||
self.page.update()
|
||||
return
|
||||
# Password strength validation
|
||||
if len(self.password.value) < 8 or not re.search(r"[A-Z]", self.password.value) or not re.search(r"[0-9]", self.password.value):
|
||||
self.message.value = "Password must be at least 8 characters long and include a number and a capital letter"
|
||||
self.page.update()
|
||||
return
|
||||
# Placeholder for register logic
|
||||
if self.password.value != self.confirm_password.value:
|
||||
self.message.value = "Passwords do not match"
|
||||
self.page.update()
|
||||
return
|
||||
self.message.value = "Registering..."
|
||||
self.page.update()
|
||||
try:
|
||||
response = requests.post(f"{API_BASE_URL}/auth/register", json={
|
||||
"name": self.name.value,
|
||||
"email": self.email.value,
|
||||
"password": self.password.value
|
||||
})
|
||||
if response.status_code == 201:
|
||||
self.message.color = ft.Colors.GREEN
|
||||
self.message.value = "Registration successful. You can now log in."
|
||||
self.page.update()
|
||||
time.sleep(3)
|
||||
self.on_back_clicked('')
|
||||
else:
|
||||
self.message.color = ft.Colors.RED
|
||||
self.message.value = response.json().get("message", "Registration failed, the user is already registered.")
|
||||
self.page.update()
|
||||
except Exception as err:
|
||||
self.message.color = ft.Colors.RED
|
||||
self.message.value = f"Error: {err}"
|
||||
self.page.update()
|
||||
|
||||
def on_back_clicked(self, e):
|
||||
self.auth.placeholder.content.clean()
|
||||
self.auth.placeholder.content = self.login.build()
|
||||
self.auth.placeholder.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Column(
|
||||
[
|
||||
ft.Text(
|
||||
"Register",
|
||||
size=30,
|
||||
weight="bold"
|
||||
),
|
||||
self.name,
|
||||
self.email,
|
||||
self.password,
|
||||
self.confirm_password,
|
||||
self.register_button,
|
||||
self.message,
|
||||
self.back_button
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
)
|
||||
389
transportmanager/client/pages/report_page.py
Normal file
389
transportmanager/client/pages/report_page.py
Normal file
@@ -0,0 +1,389 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from datetime import datetime
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ReportPage:
|
||||
def __init__(self, page: ft.Page, dashboard):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.start_date = ft.Text()
|
||||
self.end_date = ft.Text()
|
||||
# self.client_filter = ft.TextField(label="Client", expand=True)
|
||||
# self.transporter_filter = ft.TextField(label="Transporter", expand=True)
|
||||
self.status_text = ft.Text("")
|
||||
self.results_text = ft.Text("")
|
||||
self.rows = []
|
||||
self.rows_copy = []
|
||||
self.total = ft.Text("Total: ", weight=ft.FontWeight.BOLD)
|
||||
|
||||
self.data_table = ft.DataTable(
|
||||
columns=[
|
||||
ft.DataColumn(label=ft.Text("Order #")),
|
||||
ft.DataColumn(label=ft.Text("Client")),
|
||||
ft.DataColumn(label=ft.Text("Transporter")),
|
||||
ft.DataColumn(label=ft.Text("Date")),
|
||||
ft.DataColumn(label=ft.Text("Paid (€)")),
|
||||
ft.DataColumn(label=ft.Text("Received (€)")),
|
||||
ft.DataColumn(label=ft.Text("Profit (€)")),
|
||||
],
|
||||
rows=[],
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
expand=True
|
||||
)
|
||||
|
||||
self.all_clients = []
|
||||
self.all_transporters = []
|
||||
self.create_table_rows_data()
|
||||
|
||||
self.clients_filter = ft.Dropdown(
|
||||
options=[
|
||||
ft.dropdown.Option(text = client['name'], key=client['name']) for client in self.all_clients
|
||||
],
|
||||
width=250,
|
||||
label="Clients",
|
||||
hint_text= "Select client",
|
||||
on_change= self.filter_by_client
|
||||
)
|
||||
self.clients_filter_placeholder = ft.Container(content=self.clients_filter)
|
||||
|
||||
self.transporters_filter = ft.Dropdown(
|
||||
options=[
|
||||
ft.dropdown.Option(text = transporter['name'], key=transporter['name']) for transporter in self.all_transporters
|
||||
],
|
||||
width=250,
|
||||
label="Transporters",
|
||||
hint_text= "Select transporter",
|
||||
on_change= self.filter_by_transporter
|
||||
)
|
||||
self.transporters_filter_placeholder = ft.Container(content=self.transporters_filter)
|
||||
|
||||
def get_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_client(self, id):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/clients/{id}", headers=headers)
|
||||
if response.json() not in self.all_clients:
|
||||
self.all_clients.append(response.json())
|
||||
return response.json() if response.status_code == 200 else None
|
||||
except Exception as e:
|
||||
print("Error loading clients:", e)
|
||||
|
||||
def get_transporter(self, id):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/transporters/{id}", headers=headers)
|
||||
if response.json() not in self.all_transporters:
|
||||
self.all_transporters.append(response.json())
|
||||
return response.json() if response.status_code == 200 else None
|
||||
except Exception as e:
|
||||
print("Error loading transporters:", e)
|
||||
|
||||
def fetch_report(self, e):
|
||||
total = 0
|
||||
data = self.search_input.value
|
||||
self.rows_copy = self.rows
|
||||
self.data_table.rows.clear()
|
||||
buffer = []
|
||||
for r in self.rows_copy:
|
||||
if data in r[1]:
|
||||
row = ft.DataRow(
|
||||
cells=[
|
||||
ft.DataCell(ft.Text(r[0])),
|
||||
ft.DataCell(ft.Text(r[1])),
|
||||
ft.DataCell(ft.Text(r[2])),
|
||||
ft.DataCell(ft.Text(r[3])),
|
||||
ft.DataCell(ft.Text(r[4])),
|
||||
ft.DataCell(ft.Text(r[5])),
|
||||
ft.DataCell(ft.Text(r[6])),
|
||||
],
|
||||
)
|
||||
self.data_table.rows.append(row)
|
||||
buffer.append(r)
|
||||
total += r[6]
|
||||
self.rows_copy = buffer
|
||||
self.data_table.update()
|
||||
self.total.value = f"Total: {total}"
|
||||
self.total.update()
|
||||
|
||||
def create_table_rows_data(self):
|
||||
all_orders = self.get_orders()
|
||||
total = 0
|
||||
for order in all_orders:
|
||||
# Skip non-active orders from reports
|
||||
#print(order.get('status'))
|
||||
if order.get('status') != 'active':
|
||||
continue
|
||||
order_number = order['order_number']
|
||||
client_name = self.get_client(order['client_id'])['name']
|
||||
transporter_name = self.get_transporter(order['transporter_id'])['name']
|
||||
order_date = order['created_at'].split("T")[0]
|
||||
paid = order['paid_price']
|
||||
received = order['received_price']
|
||||
try:
|
||||
profit = round(float(received) - float(paid), 2)
|
||||
except:
|
||||
profit = 0.00
|
||||
|
||||
row = ft.DataRow(
|
||||
cells=[
|
||||
ft.DataCell(ft.Text(order_number)),
|
||||
ft.DataCell(ft.Text(client_name)),
|
||||
ft.DataCell(ft.Text(transporter_name)),
|
||||
ft.DataCell(ft.Text(order_date)),
|
||||
ft.DataCell(ft.Text(received)),
|
||||
ft.DataCell(ft.Text(paid)),
|
||||
ft.DataCell(ft.Text(profit)),
|
||||
],
|
||||
)
|
||||
row_data = [order_number, client_name, transporter_name, order_date, paid, received, profit]
|
||||
self.rows.append(row_data)
|
||||
self.data_table.rows.append(row)
|
||||
total += profit
|
||||
self.total.value = f"Total: {total}"
|
||||
self.rows_copy = self.rows
|
||||
|
||||
def on_reset_btn_click(self, e):
|
||||
# --- Recreate Clients dropdown to avoid sticky selection text on Flet 0.28.3 ---
|
||||
try:
|
||||
client_options = list(self.clients_filter.options)
|
||||
except Exception:
|
||||
client_options = []
|
||||
new_clients_dd = ft.Dropdown(
|
||||
options=client_options,
|
||||
width=250,
|
||||
label="Clients",
|
||||
hint_text="Select client",
|
||||
on_change=self.filter_by_client,
|
||||
value=None,
|
||||
)
|
||||
self.clients_filter = new_clients_dd
|
||||
self.clients_filter_placeholder.content = self.clients_filter
|
||||
self.clients_filter_placeholder.update()
|
||||
|
||||
# --- Recreate Transporters dropdown ---
|
||||
try:
|
||||
transporter_options = list(self.transporters_filter.options)
|
||||
except Exception:
|
||||
transporter_options = []
|
||||
new_transporters_dd = ft.Dropdown(
|
||||
options=transporter_options,
|
||||
width=250,
|
||||
label="Transporters",
|
||||
hint_text="Select transporter",
|
||||
on_change=self.filter_by_transporter,
|
||||
value=None,
|
||||
)
|
||||
self.transporters_filter = new_transporters_dd
|
||||
self.transporters_filter_placeholder.content = self.transporters_filter
|
||||
self.transporters_filter_placeholder.update()
|
||||
|
||||
self.page.update()
|
||||
self.rows_copy = list(self.rows)
|
||||
total = 0
|
||||
self.data_table.rows.clear()
|
||||
for r in self.rows_copy:
|
||||
row = ft.DataRow(
|
||||
cells=[
|
||||
ft.DataCell(ft.Text(r[0])),
|
||||
ft.DataCell(ft.Text(r[1])),
|
||||
ft.DataCell(ft.Text(r[2])),
|
||||
ft.DataCell(ft.Text(r[3])),
|
||||
ft.DataCell(ft.Text(r[4])),
|
||||
ft.DataCell(ft.Text(r[5])),
|
||||
ft.DataCell(ft.Text(r[6])),
|
||||
],
|
||||
)
|
||||
self.data_table.rows.append(row)
|
||||
total += r[6]
|
||||
self.data_table.update()
|
||||
self.total.value = f"Total: {total}"
|
||||
self.total.update()
|
||||
self.start_date.value = ""
|
||||
self.start_date.update()
|
||||
self.end_date.value = ""
|
||||
self.end_date.update()
|
||||
|
||||
def on_start_date_click(self, e):
|
||||
self.start_date.value = e.control.value.strftime('%Y-%m-%d')
|
||||
self.start_date.update()
|
||||
total = 0
|
||||
self.data_table.rows.clear()
|
||||
buffer = []
|
||||
data = datetime.strptime(self.start_date.value, '%Y-%m-%d')
|
||||
for r in self.rows_copy:
|
||||
obj_date = datetime.strptime(r[3], '%Y-%m-%d')
|
||||
if data <= obj_date:
|
||||
row = ft.DataRow(
|
||||
cells=[
|
||||
ft.DataCell(ft.Text(r[0])),
|
||||
ft.DataCell(ft.Text(r[1])),
|
||||
ft.DataCell(ft.Text(r[2])),
|
||||
ft.DataCell(ft.Text(r[3])),
|
||||
ft.DataCell(ft.Text(r[4])),
|
||||
ft.DataCell(ft.Text(r[5])),
|
||||
ft.DataCell(ft.Text(r[6])),
|
||||
],
|
||||
)
|
||||
self.data_table.rows.append(row)
|
||||
buffer.append(r)
|
||||
total += r[6]
|
||||
self.rows_copy = buffer
|
||||
self.data_table.update()
|
||||
self.total.value = f"Total: {total}"
|
||||
self.total.update()
|
||||
|
||||
def on_end_date_click(self, e):
|
||||
self.end_date.value = e.control.value.strftime('%Y-%m-%d')
|
||||
self.end_date.update()
|
||||
total = 0
|
||||
self.data_table.rows.clear()
|
||||
buffer = []
|
||||
data = datetime.strptime(self.end_date.value, '%Y-%m-%d')
|
||||
for r in self.rows_copy:
|
||||
obj_date = datetime.strptime(r[3], '%Y-%m-%d')
|
||||
if data >= obj_date:
|
||||
row = ft.DataRow(
|
||||
cells=[
|
||||
ft.DataCell(ft.Text(r[0])),
|
||||
ft.DataCell(ft.Text(r[1])),
|
||||
ft.DataCell(ft.Text(r[2])),
|
||||
ft.DataCell(ft.Text(r[3])),
|
||||
ft.DataCell(ft.Text(r[4])),
|
||||
ft.DataCell(ft.Text(r[5])),
|
||||
ft.DataCell(ft.Text(r[6])),
|
||||
],
|
||||
)
|
||||
self.data_table.rows.append(row)
|
||||
buffer.append(r)
|
||||
total += r[6]
|
||||
self.rows_copy = buffer
|
||||
self.data_table.update()
|
||||
self.total.value = f"Total: {total}"
|
||||
self.total.update()
|
||||
|
||||
def filter_by_client(self, e):
|
||||
total = 0
|
||||
self.data_table.rows.clear()
|
||||
buffer = []
|
||||
for r in self.rows_copy:
|
||||
#print(r[1])
|
||||
#print(self.clients_filter.value)
|
||||
if r[1] == self.clients_filter.value:
|
||||
row = ft.DataRow(
|
||||
cells=[
|
||||
ft.DataCell(ft.Text(r[0])),
|
||||
ft.DataCell(ft.Text(r[1])),
|
||||
ft.DataCell(ft.Text(r[2])),
|
||||
ft.DataCell(ft.Text(r[3])),
|
||||
ft.DataCell(ft.Text(r[4])),
|
||||
ft.DataCell(ft.Text(r[5])),
|
||||
ft.DataCell(ft.Text(r[6])),
|
||||
],
|
||||
)
|
||||
self.data_table.rows.append(row)
|
||||
buffer.append(r)
|
||||
total += r[6]
|
||||
self.rows_copy = buffer
|
||||
self.data_table.update()
|
||||
self.total.value = f"Total: {total}"
|
||||
self.total.update()
|
||||
|
||||
def filter_by_transporter(self, e):
|
||||
total = 0
|
||||
self.data_table.rows.clear()
|
||||
buffer = []
|
||||
for r in self.rows_copy:
|
||||
#print(r[2])
|
||||
#print(self.transporters_filter.value)
|
||||
if r[2] == self.transporters_filter.value:
|
||||
row = ft.DataRow(
|
||||
cells=[
|
||||
ft.DataCell(ft.Text(r[0])),
|
||||
ft.DataCell(ft.Text(r[1])),
|
||||
ft.DataCell(ft.Text(r[2])),
|
||||
ft.DataCell(ft.Text(r[3])),
|
||||
ft.DataCell(ft.Text(r[4])),
|
||||
ft.DataCell(ft.Text(r[5])),
|
||||
ft.DataCell(ft.Text(r[6])),
|
||||
],
|
||||
)
|
||||
self.data_table.rows.append(row)
|
||||
buffer.append(r)
|
||||
total += r[6]
|
||||
self.rows_copy = buffer
|
||||
self.data_table.update()
|
||||
self.total.value = f"Total: {total}"
|
||||
self.total.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Reports", size=24, weight=ft.FontWeight.BOLD),
|
||||
self.total
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
self.start_date,
|
||||
ft.ElevatedButton(
|
||||
"Start Date",
|
||||
on_click=lambda _: self.page.open(ft.DatePicker(
|
||||
first_date=datetime(year=2000, month=10, day=1),
|
||||
last_date=datetime(year=2025, month=10, day=1),
|
||||
on_change=self.on_start_date_click,
|
||||
)),
|
||||
width=120,
|
||||
icon=ft.Icons.CALENDAR_MONTH
|
||||
),
|
||||
self.end_date,
|
||||
ft.ElevatedButton(
|
||||
"End Date",
|
||||
on_click=lambda _: self.page.open(ft.DatePicker(
|
||||
first_date=datetime(year=2000, month=10, day=1),
|
||||
last_date=datetime(year=2025, month=10, day=1),
|
||||
on_change=self.on_end_date_click,
|
||||
)),
|
||||
width=120,
|
||||
icon=ft.Icons.CALENDAR_MONTH
|
||||
),
|
||||
ft.Text(),
|
||||
self.clients_filter_placeholder,
|
||||
self.transporters_filter_placeholder,
|
||||
ft.ElevatedButton("Reset", on_click=self.on_reset_btn_click, width=120),
|
||||
]
|
||||
),
|
||||
ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
self.data_table,
|
||||
self.status_text,
|
||||
],
|
||||
expand=True,
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
scroll=ft.ScrollMode.ADAPTIVE,
|
||||
expand=True,
|
||||
)
|
||||
],
|
||||
expand=True,
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
),
|
||||
expand=True
|
||||
)
|
||||
68
transportmanager/client/pages/reset_password_page.py
Normal file
68
transportmanager/client/pages/reset_password_page.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import re
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ResetPasswordPage:
|
||||
def __init__(self, page: ft.Page):
|
||||
self.page = page
|
||||
self.page.update()
|
||||
self.token = None
|
||||
if '/reset_password?token=' in self.page.route:
|
||||
self.token = self.page.route.split('?token=')[1]
|
||||
self.password = ft.TextField(label="New Password", password=True, can_reveal_password=True)
|
||||
self.confirm_password = ft.TextField(label="Confirm Password", password=True, can_reveal_password=True)
|
||||
self.message = ft.Text("")
|
||||
self.submit_btn = ft.ElevatedButton("Reset Password", on_click=self.on_submit)
|
||||
|
||||
def on_submit(self, e):
|
||||
new_password = self.password.value.strip()
|
||||
confirm_password = self.confirm_password.value.strip()
|
||||
|
||||
# Password strength validation
|
||||
if len(new_password) < 8 or not re.search(r"[A-Z]", new_password) or not re.search(r"[0-9]", new_password):
|
||||
self.message.value = "Password must be at least 8 characters long and include a number and a capital letter."
|
||||
self.page.update()
|
||||
return
|
||||
|
||||
if not new_password or not confirm_password:
|
||||
self.message.value = "Both fields are required."
|
||||
elif new_password != confirm_password:
|
||||
self.message.value = "Passwords do not match."
|
||||
elif len(new_password) < 6:
|
||||
self.message.value = "Password must be at least 6 characters."
|
||||
else:
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{API_BASE_URL}/auth/reset_password",
|
||||
json={"token": self.token, "new_password": new_password},
|
||||
timeout=10
|
||||
)
|
||||
if response.status_code == 200:
|
||||
self.message.value = "Password reset successfully. Please login."
|
||||
else:
|
||||
self.message.value = "Invalid or expired token."
|
||||
except Exception as ex:
|
||||
self.message.value = f"Request failed: {ex}"
|
||||
|
||||
self.page.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("Reset Your Password", style=ft.TextThemeStyle.HEADLINE_MEDIUM),
|
||||
self.password,
|
||||
self.confirm_password,
|
||||
self.message,
|
||||
self.submit_btn
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
expand=True,
|
||||
),
|
||||
expand=True,
|
||||
alignment=ft.alignment.center,
|
||||
padding=20,
|
||||
width=350
|
||||
)
|
||||
118
transportmanager/client/pages/send_email_page.py
Normal file
118
transportmanager/client/pages/send_email_page.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
import json
|
||||
from config import API_BASE_URL
|
||||
|
||||
class SendEmail:
|
||||
def __init__(self, page: ft.Page, view_page, dashboard, order_id):
|
||||
self.page = page
|
||||
self.view_page = view_page
|
||||
self.dashboard = dashboard
|
||||
self.order_id = order_id
|
||||
self.smtp_password = ft.TextField(label="PASSWORD")
|
||||
self.message = ft.TextField(label="Message", multiline=True, min_lines=10, max_lines=20, expand=True)
|
||||
self.send = ft.ElevatedButton("Send", width=150, on_click=self.send_email)
|
||||
self.order = self.get_order()
|
||||
if self.order:
|
||||
transporter = self.get_transporter(self.order['transporter_id'])
|
||||
self.to_email = transporter['email']
|
||||
self.smtp_host = ''
|
||||
self.smtp_port = ''
|
||||
self.from_email = ''
|
||||
|
||||
def on_go_back_btn_click(self, e):
|
||||
self.dashboard.placeholder.content = self.view_page.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def send_email(self, e):
|
||||
user_id = self.page.session.get("user_id")
|
||||
user = self.get_user()
|
||||
transporter_data = {
|
||||
"to_email":self.to_email,
|
||||
"subject":self.order['order_number'],
|
||||
"body":self.message.value,
|
||||
"filename":f'order_{user_id}_{self.order['order_number']}.pdf',
|
||||
"smtp_host":self.smtp_host,
|
||||
"smtp_port":self.smtp_port,
|
||||
"smtp_user":self.from_email,
|
||||
"smtp_pass": self.smtp_password.value,
|
||||
}
|
||||
self.send_email_custom(transporter_data)
|
||||
user_data = {
|
||||
"to_email":user['email'],
|
||||
"subject":self.order['order_number'],
|
||||
"body":self.message.value,
|
||||
"filename":f'order_{user_id}_{self.order['order_number']}.pdf',
|
||||
"smtp_host":self.smtp_host,
|
||||
"smtp_port":self.smtp_port,
|
||||
"smtp_user":self.from_email,
|
||||
"smtp_pass": self.smtp_password.value,
|
||||
}
|
||||
self.send_email_custom(user_data)
|
||||
|
||||
def send_email_custom(self, data):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/orders/send-email/custom", headers=headers, data=json.dumps(data))
|
||||
return response.json() if response.status_code == 200 else None
|
||||
|
||||
def get_transporter(self, id):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
user_id = self.page.session.get("user_id")
|
||||
response = requests.get(f"{API_BASE_URL}/transporters/{id}", headers=headers)
|
||||
return response.json() if response.status_code == 200 else None
|
||||
|
||||
def get_order(self):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/orders/{self.order_id}", headers=headers)
|
||||
return response.json() if response.status_code == 200 else None
|
||||
|
||||
def get_user(self):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/profile", headers=headers)
|
||||
return response.json() if response.status_code == 200 else None
|
||||
|
||||
def get_email_credentials(self):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/profile/email", headers=headers)
|
||||
credentials = response.json()
|
||||
self.smtp_host = credentials['smtp_host']
|
||||
self.smtp_port = credentials['smtp_port']
|
||||
self.from_email = credentials['smtp_user']
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Send E-mail", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Column(
|
||||
[
|
||||
ft.Text("Please insert email password:"),
|
||||
self.smtp_password,
|
||||
]
|
||||
),
|
||||
self.send
|
||||
],
|
||||
vertical_alignment=ft.CrossAxisAlignment.END,
|
||||
expand=True
|
||||
),
|
||||
self.message
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
scroll=ft.ScrollMode.ADAPTIVE,
|
||||
expand=True
|
||||
),
|
||||
expand=True
|
||||
)
|
||||
247
transportmanager/client/pages/subscription_page.py
Normal file
247
transportmanager/client/pages/subscription_page.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class Subscription:
|
||||
def __init__(self, page: ft.Page, dashboard):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.subscription = self.get_current_subscription_plan()
|
||||
self.plan = {
|
||||
'first_2_months':'First Two Months' ,
|
||||
'monthly':'Monthly',
|
||||
'yearly':'Yearly'
|
||||
}
|
||||
self.status = {
|
||||
'active':'Active',
|
||||
'cancelled':'Cancelled',
|
||||
'expired':'Expired',
|
||||
'less_than_5_days':'Less than 5 days'
|
||||
}
|
||||
self.current_subscription_plan = ft.Text(self.plan[self.subscription['plan']] if self.subscription else "No subscription")
|
||||
self.current_subscription_status = ft.Text(self.status[self.subscription['status']] if self.subscription else "None")
|
||||
self.monthly_subscription_price = ft.Text(
|
||||
"100 Euro/Month",
|
||||
weight=ft.FontWeight.BOLD,
|
||||
size=18,
|
||||
color=ft.Colors.WHITE
|
||||
)
|
||||
self.year_subscription_price = ft.Text(
|
||||
"1000 Euro/Year",
|
||||
weight=ft.FontWeight.BOLD,
|
||||
size=18,
|
||||
color=ft.Colors.WHITE
|
||||
)
|
||||
self.first_subscription_price = ft.Text(
|
||||
"0 Euro/Month",
|
||||
weight=ft.FontWeight.BOLD,
|
||||
size=18,
|
||||
color=ft.Colors.WHITE
|
||||
)
|
||||
|
||||
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 on_first_two_months_btn_click(self, e):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{API_BASE_URL}/subscription/first_2_months", headers=headers, )
|
||||
#print(response.text)
|
||||
self.change_subscription_to_active('first_2_months', 'active')
|
||||
return response.json() if response.status_code == 200 else None
|
||||
except Exception as e:
|
||||
print("Error loading subscription:", e)
|
||||
|
||||
def on_month_subscription_btn_click(self, e):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{API_BASE_URL}/subscription/one_month", headers=headers)
|
||||
#print(response.text)
|
||||
self.change_subscription_to_active('monthly', 'active')
|
||||
return response.json() if response.status_code == 200 else None
|
||||
except Exception as e:
|
||||
print("Error loading subscription:", e)
|
||||
|
||||
def on_year_subscription_btn_click(self, e):
|
||||
try:
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.post(f"{API_BASE_URL}/subscription/one_year", headers=headers)
|
||||
#print(response.text)
|
||||
self.change_subscription_to_active('yearly', 'active')
|
||||
return response.json() if response.status_code == 200 else None
|
||||
except Exception as e:
|
||||
print("Error loading subscription:", e)
|
||||
|
||||
def change_subscription_to_active(self, plan, status):
|
||||
self.dashboard.subscription_status_bottom.value = "Active"
|
||||
self.dashboard.subscription_status_bottom.color = ft.Colors.GREEN
|
||||
self.dashboard.subscription_status_bottom.update()
|
||||
|
||||
self.current_subscription_plan.value = self.plan[plan]
|
||||
self.current_subscription_plan.update()
|
||||
|
||||
self.current_subscription_status.value = self.status[status]
|
||||
self.current_subscription_status.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Subscription", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Current Subscription Plan:", weight=ft.FontWeight.BOLD),
|
||||
self.current_subscription_plan
|
||||
]
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Subscription Status:"),
|
||||
self.current_subscription_status
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.Container(
|
||||
content = ft.Column(
|
||||
[
|
||||
ft.Icon(
|
||||
name = ft.Icons.AUTORENEW,
|
||||
size=150
|
||||
),
|
||||
ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("First Two Months", weight=ft.FontWeight.BOLD),
|
||||
self.first_subscription_price
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
expand=True
|
||||
),
|
||||
bgcolor=ft.Colors.BLUE_200,
|
||||
padding=20,
|
||||
width=250
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.FilledButton("Add", width=150, on_click=self.on_first_two_months_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
expand=True
|
||||
)
|
||||
],
|
||||
expand = True ,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
),
|
||||
border_radius=20,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
padding = ft.padding.symmetric(vertical=20),
|
||||
width=250,
|
||||
height=350
|
||||
),
|
||||
ft.Container(
|
||||
content = ft.Column(
|
||||
[
|
||||
ft.Icon(
|
||||
name = ft.Icons.AUTORENEW,
|
||||
size=150
|
||||
),
|
||||
ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("One Month Subscription", weight=ft.FontWeight.BOLD),
|
||||
self.monthly_subscription_price
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
expand=True
|
||||
),
|
||||
bgcolor=ft.Colors.BLUE_200,
|
||||
padding=20,
|
||||
width=250
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.FilledButton("Add / Renew", width=150, on_click=self.on_month_subscription_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
expand=True
|
||||
)
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
),
|
||||
border_radius=20,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
padding = ft.padding.symmetric(vertical=20),
|
||||
width=250,
|
||||
height=350
|
||||
),
|
||||
ft.Container(
|
||||
content = ft.Column(
|
||||
[
|
||||
ft.Icon(
|
||||
name = ft.Icons.AUTORENEW,
|
||||
size=150
|
||||
),
|
||||
ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Text("One Year Subscription", weight=ft.FontWeight.BOLD),
|
||||
self.year_subscription_price
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
expand=True
|
||||
),
|
||||
bgcolor=ft.Colors.BLUE_200,
|
||||
padding=20,
|
||||
width=250
|
||||
),
|
||||
ft.Row(
|
||||
[
|
||||
ft.FilledButton("Add / Renew", width=150, on_click=self.on_year_subscription_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
expand=True
|
||||
)
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER
|
||||
),
|
||||
border_radius=20,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
padding = ft.padding.symmetric(vertical=20),
|
||||
width=250,
|
||||
height=350
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
spacing=20
|
||||
)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
expand=True,
|
||||
spacing=50
|
||||
),
|
||||
expand=True
|
||||
)
|
||||
273
transportmanager/client/pages/transporters_page.py
Normal file
273
transportmanager/client/pages/transporters_page.py
Normal file
@@ -0,0 +1,273 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class TransportersPage:
|
||||
def __init__(self, page: ft.Page, dashboard):
|
||||
self.page = page
|
||||
self.dashboard = dashboard
|
||||
self.transporters = []
|
||||
self.dialog = None
|
||||
self.name = ft.TextField(
|
||||
label="Name",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.street_and_number = ft.TextField(
|
||||
label="Street and number",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.postal_code = ft.TextField(
|
||||
label="Postal code",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.city = ft.TextField(
|
||||
label="City",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.region_county = ft.TextField(
|
||||
label="Region / County",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.country = ft.TextField(
|
||||
label="Country",
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.register_number = ft.TextField(
|
||||
label="Register Number",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.contact_person = ft.TextField(
|
||||
label="Contact Person",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.phone = ft.TextField(
|
||||
label="Phone",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.email = ft.TextField(
|
||||
label="Email",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.vat = ft.TextField(
|
||||
label="VAT",
|
||||
expand=True,
|
||||
input_filter=ft.InputFilter(
|
||||
allow=True,
|
||||
regex_string=r"^[\x20-\x7E]*$",
|
||||
replacement_string=""
|
||||
),
|
||||
)
|
||||
self.user_id = self.page.session.get("user_id")
|
||||
self.selected_id = None
|
||||
self.subscription_error = ft.Text("Please subscribe to add new transporter", color=ft.Colors.RED)
|
||||
|
||||
def open_dialog(self, transporter=None):
|
||||
if transporter:
|
||||
self.selected_id = transporter["id"]
|
||||
self.name.value = transporter["name"]
|
||||
self.street_and_number.value = transporter["address"].split(" %")[0]
|
||||
self.postal_code.value = transporter["address"].split(" %")[1]
|
||||
self.city.value = transporter["address"].split(" %")[2]
|
||||
self.region_county.value = transporter["address"].split(" %")[3]
|
||||
self.country.value = transporter["address"].split(" %")[4]
|
||||
self.register_number.value = transporter["register_number"]
|
||||
self.contact_person.value = transporter["contact_person"]
|
||||
self.phone.value = transporter["phone"]
|
||||
self.email.value = transporter["email"]
|
||||
self.vat.value = transporter["vat"]
|
||||
else:
|
||||
self.selected_id = None
|
||||
self.name.value = ""
|
||||
self.street_and_number.value = ""
|
||||
self.postal_code.value = ""
|
||||
self.city.value = ""
|
||||
self.region_county.value = ""
|
||||
self.country.value = ""
|
||||
self.register_number.value = ""
|
||||
self.contact_person.value = ""
|
||||
self.phone.value = ""
|
||||
self.email.value = ""
|
||||
self.vat.value = ""
|
||||
|
||||
self.dialog = ft.AlertDialog(
|
||||
modal=True,
|
||||
title=ft.Text("Transporter"),
|
||||
content=ft.Column(
|
||||
controls=[
|
||||
self.name,
|
||||
self.register_number,
|
||||
self.vat,
|
||||
self.contact_person,
|
||||
self.phone,
|
||||
self.email,
|
||||
self.street_and_number,
|
||||
self.postal_code,
|
||||
self.city,
|
||||
self.region_county,
|
||||
self.country,
|
||||
],
|
||||
width=600
|
||||
),
|
||||
actions=[
|
||||
ft.TextButton("Cancel", on_click=lambda e: self.page.close(self.dialog)),
|
||||
ft.ElevatedButton("Save", on_click=self.save_transporter)
|
||||
],
|
||||
)
|
||||
self.page.dialog = self.dialog
|
||||
self.page.open(self.dialog)
|
||||
|
||||
def save_transporter(self, e):
|
||||
address = f'{self.street_and_number.value} %{self.postal_code.value} %{self.city.value} %{self.region_county.value} %{self.country.value}'
|
||||
|
||||
data = {
|
||||
"name": self.name.value,
|
||||
"address": address,
|
||||
"register_number": self.register_number.value,
|
||||
"contact_person": self.contact_person.value,
|
||||
"phone": self.phone.value,
|
||||
"email": self.email.value,
|
||||
"vat": self.vat.value,
|
||||
"user_id": self.user_id
|
||||
}
|
||||
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
|
||||
if self.selected_id:
|
||||
requests.put(f"{API_BASE_URL}/transporters/{self.selected_id}", json=data, headers=headers)
|
||||
else:
|
||||
requests.post(f"{API_BASE_URL}/transporters/", json=data, headers=headers)
|
||||
|
||||
self.page.close(self.dialog)
|
||||
self.refresh()
|
||||
self.page.update()
|
||||
|
||||
def delete_transporter(self, transporter_id):
|
||||
def confirm_delete(e):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
requests.delete(f"{API_BASE_URL}/transporters/{transporter_id}", headers=headers)
|
||||
self.page.close(self.confirm_dialog)
|
||||
self.refresh()
|
||||
|
||||
self.confirm_dialog = ft.AlertDialog(
|
||||
title=ft.Text("Confirm"),
|
||||
content=ft.Text("Are you sure you want to delete this transporter?"),
|
||||
actions=[
|
||||
ft.ElevatedButton("Yes", on_click=confirm_delete),
|
||||
ft.ElevatedButton("No", on_click=lambda e: self.page.close(self.confirm_dialog))
|
||||
]
|
||||
)
|
||||
self.page.dialog = self.confirm_dialog
|
||||
self.page.open(self.confirm_dialog)
|
||||
|
||||
def refresh(self):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/transporters/", headers=headers)
|
||||
if response.ok:
|
||||
self.transporters = response.json()
|
||||
else:
|
||||
self.transporters = []
|
||||
|
||||
self.transporter_list.controls.clear()
|
||||
for transporter in self.transporters:
|
||||
self.transporter_list.controls.append(
|
||||
ft.Container(
|
||||
content=ft.Row([
|
||||
ft.Column([
|
||||
ft.Text(f"{transporter['name']}", size=16, weight=ft.FontWeight.BOLD),
|
||||
ft.Text(f"{transporter['email']} • {transporter['phone']}", size=12)
|
||||
], expand=True),
|
||||
ft.IconButton(icon=ft.Icons.EDIT, on_click=lambda e, t=transporter: self.open_dialog(t)),
|
||||
ft.IconButton(icon=ft.Icons.DELETE, on_click=lambda e, t=transporter: self.delete_transporter(t["id"])),
|
||||
]),
|
||||
padding=10,
|
||||
border=ft.border.all(1, ft.Colors.GREY_300),
|
||||
bgcolor=ft.Colors.BLUE_50,
|
||||
border_radius=10,
|
||||
)
|
||||
)
|
||||
self.page.update()
|
||||
|
||||
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.transporter_list = ft.Column(spacing=10, expand=True, scroll=ft.ScrollMode.ADAPTIVE,)
|
||||
self.refresh()
|
||||
self.add_transporter_btn = ft.ElevatedButton("Add Transporter", icon=ft.Icons.ADD, on_click=lambda e: self.open_dialog())
|
||||
self.header = ft.Row(
|
||||
controls=[
|
||||
ft.Text("Transporters", size=24, weight=ft.FontWeight.BOLD, expand=True),
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
)
|
||||
subscription = self.get_current_subscription_plan()
|
||||
if subscription:
|
||||
if subscription['status'] != 'expired':
|
||||
self.header.controls.append(self.add_transporter_btn)
|
||||
else:
|
||||
self.header.controls.append(self.subscription_error)
|
||||
else:
|
||||
self.header.controls.append(self.subscription_error)
|
||||
return ft.Column(
|
||||
[
|
||||
self.header,
|
||||
self.transporter_list
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.START,
|
||||
|
||||
)
|
||||
70
transportmanager/client/pages/two_factor_page.py
Normal file
70
transportmanager/client/pages/two_factor_page.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import flet as ft
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class TwoFactorAuth:
|
||||
def __init__(self, page: ft.Page, email: str, login, auth):
|
||||
self.page = page
|
||||
self.email = email
|
||||
self.auth = auth
|
||||
self.login = login
|
||||
self.code_field = ft.TextField(label="Verification Code")
|
||||
self.error_text = ft.Text(value="", color=ft.Colors.RED)
|
||||
self.success_text = ft.Text(value="", color=ft.Colors.GREEN)
|
||||
self.verify_button = ft.ElevatedButton(text="Verify", on_click=self.on_verify_click, width=150)
|
||||
self.back_button = ft.TextButton(text="Back to Login", on_click=self.on_back_clicked)
|
||||
|
||||
def on_verify_click(self, e):
|
||||
code = self.code_field.value
|
||||
|
||||
if not code:
|
||||
self.error_text.value = "Please enter the verification code."
|
||||
self.page.update()
|
||||
return
|
||||
|
||||
try:
|
||||
response = requests.post(f"{API_BASE_URL}/auth/verify_code", json={"email": self.email, "code": code})
|
||||
if response.status_code == 200:
|
||||
token = response.json().get("access_token")
|
||||
self.page.client_storage.set("token", token)
|
||||
user_info = requests.get(f"{API_BASE_URL}/auth/me", headers={"Authorization": f"Bearer {token}"}).json()
|
||||
self.page.session.set("user_id", user_info.get("id"))
|
||||
self.page.session.set("first_order_number", user_info['first_order_number'])
|
||||
#print(user_info.get("user_role"))
|
||||
if user_info.get("user_role") == 'admin':
|
||||
print('Admin Logged In')
|
||||
self.page.go("/admin")
|
||||
else:
|
||||
self.success_text.value = "Verification successful. You are now logged in."
|
||||
self.error_text.value = ""
|
||||
self.page.update()
|
||||
self.page.go("/dashboard") # Change this to your main page
|
||||
else:
|
||||
self.error_text.value = "Invalid or expired code."
|
||||
self.success_text.value = ""
|
||||
self.page.update()
|
||||
except Exception as err:
|
||||
self.error_text.value = f"Error: {err}"
|
||||
self.success_text.value = ""
|
||||
self.page.update()
|
||||
|
||||
def on_back_clicked(self, e):
|
||||
self.auth.placeholder.content.clean()
|
||||
self.auth.placeholder.content = self.login.build()
|
||||
self.auth.placeholder.update()
|
||||
|
||||
def build(self):
|
||||
return ft.Column(
|
||||
[
|
||||
ft.Text(value=f"Enter the code sent to {self.email}", size=18),
|
||||
self.code_field,
|
||||
self.verify_button,
|
||||
self.error_text,
|
||||
self.success_text,
|
||||
self.back_button
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
alignment=ft.MainAxisAlignment.CENTER,
|
||||
spacing=20,
|
||||
width=350
|
||||
)
|
||||
1215
transportmanager/client/pages/view_orders_in_page.py
Normal file
1215
transportmanager/client/pages/view_orders_in_page.py
Normal file
File diff suppressed because it is too large
Load Diff
50
transportmanager/client/pages/view_page.py
Normal file
50
transportmanager/client/pages/view_page.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import flet as ft
|
||||
from flet_webview import WebView
|
||||
from pages.send_email_page import SendEmail
|
||||
import requests
|
||||
from config import API_BASE_URL
|
||||
|
||||
class ViewPage:
|
||||
def __init__(self, page: ft.Page, pdf_name, order_page, dashboard, order_id):
|
||||
self.page = page
|
||||
self.pdf_name = pdf_name
|
||||
self.order_page = order_page
|
||||
self.dashboard = dashboard
|
||||
self.order_id = order_id
|
||||
self.send_email = SendEmail(self.page, self, self.dashboard, self.order_id)
|
||||
self.view_content = WebView(url=f"{API_BASE_URL}/orders/pdfs/{self.pdf_name}", expand=True)
|
||||
self.send_btn = ft.FilledButton("Send as Email", on_click=self.on_send_email_btn_click)
|
||||
self.row_btn = ft.Row([])
|
||||
if self.get_credentials():
|
||||
self.row_btn.controls.append(self.send_btn)
|
||||
|
||||
def on_go_back_btn_click(self, e):
|
||||
self.dashboard.placeholder.content = self.order_page.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def on_send_email_btn_click(self, e):
|
||||
self.dashboard.placeholder.content = self.send_email.build()
|
||||
self.dashboard.placeholder.update()
|
||||
|
||||
def get_credentials(self):
|
||||
token = self.page.client_storage.get("token")
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
response = requests.get(f"{API_BASE_URL}/profile/email", headers=headers)
|
||||
return response.json() if response.status_code == 200 else None
|
||||
|
||||
def build(self):
|
||||
return ft.Container(
|
||||
content=ft.Column(
|
||||
[
|
||||
ft.Row(
|
||||
[
|
||||
ft.Text("Order", size=24, weight=ft.FontWeight.BOLD),
|
||||
ft.Button("Back", icon=ft.Icons.ARROW_BACK_IOS_NEW, on_click=self.on_go_back_btn_click)
|
||||
],
|
||||
alignment=ft.MainAxisAlignment.SPACE_BETWEEN
|
||||
),
|
||||
self.view_content,
|
||||
self.row_btn,
|
||||
]
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user