feat: initial LMS frontend with adaptive navigation and routing
This commit is contained in:
+23
@@ -0,0 +1,23 @@
|
||||
import requests
|
||||
|
||||
API_BASE_URL = "http://127.0.0.1:8000"
|
||||
|
||||
def login(username: str, password: str):
|
||||
url = f"{API_BASE_URL}/api/auth/login/"
|
||||
|
||||
response = requests.post(url, json={
|
||||
"email": username,
|
||||
"password": password
|
||||
})
|
||||
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
|
||||
def get_me(token: str):
|
||||
url = f"{API_BASE_URL}/api/auth/me/"
|
||||
response = requests.get(
|
||||
url,
|
||||
headers={"Authorization": f"Bearer {token}"}
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
@@ -0,0 +1,30 @@
|
||||
import flet as ft
|
||||
|
||||
from app.widgets.adaptive_menu import adaptive_menu
|
||||
|
||||
def main_layout(page: ft.Page, content_container: ft.Container):
|
||||
print(">>> ENTER main_layout")
|
||||
menu = adaptive_menu(page)
|
||||
|
||||
# ตรวจสอบโหมด (ป้องกันหน้าจอขาวจาก width=0)
|
||||
width = page.width if page.width > 0 else 1024 # default ไว้ที่ desktop ก่อนถ้ายังไม่รู้ width
|
||||
is_mobile = width < 600
|
||||
print(f">>> Device mode: {'MOBILE' if is_mobile else 'DESKTOP'} (Width: {page.width})")
|
||||
|
||||
if is_mobile:
|
||||
return ft.Column(
|
||||
controls=[
|
||||
content_container,
|
||||
menu
|
||||
],
|
||||
expand=True
|
||||
)
|
||||
else:
|
||||
return ft.Row(
|
||||
controls=[
|
||||
menu,
|
||||
ft.VerticalDivider(width=1),
|
||||
content_container
|
||||
],
|
||||
expand=True
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
import flet as ft
|
||||
|
||||
def articles_page():
|
||||
return ft.Column([
|
||||
ft.Text("บทความ", size=22),
|
||||
ft.Text("รายการบทความจะมาอยู่ตรงนี้"),
|
||||
])
|
||||
@@ -0,0 +1,7 @@
|
||||
import flet as ft
|
||||
|
||||
def courses_page():
|
||||
return ft.Column([
|
||||
ft.Text("หลักสูตร", size=22),
|
||||
ft.Text("รายการหลักสูตร")
|
||||
])
|
||||
@@ -0,0 +1,69 @@
|
||||
import flet as ft
|
||||
import asyncio
|
||||
|
||||
from app.api import login, get_me
|
||||
from app.state import state
|
||||
|
||||
def login_page(page: ft.Page, on_login_success):
|
||||
username = ft.TextField(
|
||||
label="Email",
|
||||
on_submit=lambda _: handle_login(), # เรียก wrapper
|
||||
autofocus=True
|
||||
)
|
||||
|
||||
password = ft.TextField(
|
||||
label="Password",
|
||||
password=True,
|
||||
can_reveal_password=True,
|
||||
on_submit=lambda _: handle_login(), # เรียก wrapper
|
||||
)
|
||||
|
||||
message = ft.Text()
|
||||
|
||||
# WRAPPER (sync)
|
||||
def handle_login(e=None):
|
||||
asyncio.create_task(do_login()) # ยิง async
|
||||
|
||||
# ASYNC ตัวจริง
|
||||
async def do_login():
|
||||
if not username.value or not password.value:
|
||||
message.value = "กรุณากรอกข้อมูลให้ครบถ้วน"
|
||||
message.color = ft.Colors.RED_400
|
||||
page.update()
|
||||
return
|
||||
|
||||
try:
|
||||
message.value = "กำลังตรวจสอบข้อมูล..."
|
||||
message.color = ft.Colors.BLUE_400
|
||||
page.update()
|
||||
|
||||
result = await asyncio.to_thread(login, username.value, password.value)
|
||||
state.access_token = result["access"]
|
||||
|
||||
state.user = await asyncio.to_thread(get_me, state.access_token)
|
||||
|
||||
await on_login_success() # ไป route
|
||||
|
||||
except Exception as e:
|
||||
message.value = "เข้าสู่ระบบไม่สำเร็จ"
|
||||
message.color = ft.Colors.RED_400
|
||||
print("LOGIN ERROR:", e)
|
||||
page.update()
|
||||
|
||||
login_button = ft.FilledButton(
|
||||
"ล็อกอิน",
|
||||
on_click=handle_login, # ใช้ wrapper
|
||||
width=200,
|
||||
)
|
||||
|
||||
return ft.Column(
|
||||
controls=[
|
||||
ft.Text("เข้าสู่ระบบ", size=24),
|
||||
username,
|
||||
password,
|
||||
login_button,
|
||||
message,
|
||||
],
|
||||
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
|
||||
spacing=20,
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
import flet as ft
|
||||
|
||||
def my_courses_page():
|
||||
return ft.Column([
|
||||
ft.Text('หลักสูตรของฉัน', size=22),
|
||||
ft.Text('ข้อมูลหลักสูตรที่ลงทะเบียน')
|
||||
])
|
||||
@@ -0,0 +1,7 @@
|
||||
import flet as ft
|
||||
|
||||
def profile_page():
|
||||
return ft.Column([
|
||||
ft.Text("ข้อมูลผู้ใช้งาน", size=22),
|
||||
ft.Text("รายละเอียดข้อมูลผู้ใช้งานจะมาอยู่ตรงนี้"),
|
||||
])
|
||||
@@ -0,0 +1,7 @@
|
||||
import flet as ft
|
||||
|
||||
def progress_page():
|
||||
return ft.Column([
|
||||
ft.Text('ความก้าวหน้าของฉัน', size=22),
|
||||
ft.Text('รายละเอียดความก้าวหน้าของฉัน')
|
||||
])
|
||||
@@ -0,0 +1,6 @@
|
||||
class AppState:
|
||||
def __init__(self):
|
||||
self.access_token = None
|
||||
self.user = None
|
||||
|
||||
state = AppState()
|
||||
@@ -0,0 +1,58 @@
|
||||
import flet as ft
|
||||
|
||||
def adaptive_menu(page: ft.Page):
|
||||
is_mobile = page.width < 600
|
||||
|
||||
menu_keys = ['articles', 'courses', 'my_courses', 'progress', 'profile', 'logout']
|
||||
|
||||
async def handle_change(e):
|
||||
index = int(e.control.selected_index)
|
||||
key = menu_keys[index]
|
||||
|
||||
if key == "logout":
|
||||
from app.state import state
|
||||
state.access_token = None
|
||||
await page.push_route("/login") # หรือ page.go
|
||||
return
|
||||
|
||||
route = f"/{menu_keys[index]}"
|
||||
print(f">>> Navigating to: {route}")
|
||||
await page.push_route(route)
|
||||
|
||||
# sync selected index กับ route
|
||||
current_route = page.route.replace("/", "") or "articles"
|
||||
|
||||
if current_route not in menu_keys:
|
||||
current_route = "articles"
|
||||
|
||||
current_selected_index = menu_keys.index(current_route)
|
||||
|
||||
if is_mobile:
|
||||
return ft.NavigationBar(
|
||||
destinations=[
|
||||
ft.NavigationBarDestination(icon=ft.Icons.ARTICLE, label='บทความ'),
|
||||
ft.NavigationBarDestination(icon=ft.Icons.SCHOOL, label='หลักสูตร'),
|
||||
ft.NavigationBarDestination(icon=ft.Icons.BOOK, label='คอร์สของฉัน'),
|
||||
ft.NavigationBarDestination(icon=ft.Icons.TRENDING_UP, label='ความคืบหน้า'),
|
||||
ft.NavigationBarDestination(icon=ft.Icons.PERSON, label='โปรไฟล์'),
|
||||
ft.NavigationBarDestination(icon=ft.Icons.LOGOUT, label="ออกจากระบบ"
|
||||
),
|
||||
],
|
||||
selected_index=current_selected_index,
|
||||
on_change=handle_change,
|
||||
)
|
||||
else:
|
||||
return ft.NavigationRail(
|
||||
destinations=[
|
||||
ft.NavigationRailDestination(icon=ft.Icons.ARTICLE, label='บทความ'),
|
||||
ft.NavigationRailDestination(icon=ft.Icons.SCHOOL, label='หลักสูตร'),
|
||||
ft.NavigationRailDestination(icon=ft.Icons.BOOK, label='คอร์สของฉัน'),
|
||||
ft.NavigationRailDestination(icon=ft.Icons.TRENDING_UP, label='ความคืบหน้า'),
|
||||
ft.NavigationRailDestination(icon=ft.Icons.PERSON, label='โปรไฟล์'),
|
||||
ft.NavigationRailDestination(icon=ft.Icons.LOGOUT, label="Logout"
|
||||
),
|
||||
],
|
||||
selected_index=current_selected_index,
|
||||
on_change=handle_change,
|
||||
extended=True,
|
||||
)
|
||||
Reference in New Issue
Block a user