diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/__init__.py b/app/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/api/articles.py b/app/api/articles.py new file mode 100644 index 0000000..b0d5567 --- /dev/null +++ b/app/api/articles.py @@ -0,0 +1,38 @@ +import requests + +API_BASE_URL = 'http://localhost:8000/api' + +def list_articles(page=1, search=None, token=None): + params = {'page' : page} + + if search: + params['search'] = search + + headers = {} + if token: + headers['Authorization'] = f"Bearer {token}" + + r = requests.get( + f"{API_BASE_URL}/articles", + params=params, + headers=headers, + timeout=10 + ) + + r.raise_for_status() + return r.json() + +def get_article(article_id, token=None): + headers={} + + if token: + headers['Authorization'] = f"Bearer {token}" + + r = requests.get( + f"{API_BASE_URL}/articles/{article_id}", + headers = headers, + timeout = 10 + ) + + r.raise_for_status() + return r.json() \ No newline at end of file diff --git a/app/api.py b/app/api/auth.py similarity index 100% rename from app/api.py rename to app/api/auth.py diff --git a/app/pages/article_detail.py b/app/pages/article_detail.py new file mode 100644 index 0000000..be74612 --- /dev/null +++ b/app/pages/article_detail.py @@ -0,0 +1,35 @@ +import asyncio + +import flet as ft + +from app.api.articles import get_article +from app.state import state + + +def article_detail_page(page: ft.Page, article_id: int): + content = ft.Column(expand=True) + + async def load_article(): + content.controls.clear() + content.controls.append(ft.ProgressRing()) + page.update() + + article = await asyncio.to_thread( + get_article, + article_id, + state.access_token + ) + + content.controls.clear() + content.controls.extend([ + ft.Text(article['title'], size=16, weight=ft.FontWeight.BOLD), + ft.Text(article['body']), + ft.FilledButton( + 'ย้อนกลับ', + on_click=lambda e: asyncio.create_task(page.push_route('/articles')) + ) + ]) + page.update() + + asyncio.create_task(load_article()) + return content \ No newline at end of file diff --git a/app/pages/articles.py b/app/pages/articles.py index 18aa138..4be350e 100644 --- a/app/pages/articles.py +++ b/app/pages/articles.py @@ -1,7 +1,107 @@ +import asyncio + import flet as ft -def articles_page(): - return ft.Column([ - ft.Text("บทความ", size=22), - ft.Text("รายการบทความจะมาอยู่ตรงนี้"), - ]) \ No newline at end of file +from app.api.articles import list_articles + +from app.state import state + +def article_card(page, article): + async def go_detail(e): + await page.push_route(f"/articles/{article['id']}") + + return ft.Card( + content=ft.Container( + padding=15, + content=ft.Column( + controls=[ + ft.Text(article['title'], size=18, weight=ft.FontWeight.BOLD), + ft.Text(article.get('body', ''), max_lines=2), + ft.TextButton( + 'อ่านต่อ', + on_click=go_detail, + ) + ] + ) + ) + ) + +def articles_page(page: ft.Page): + articles_column = ft.Column(expand=True, spacing=10) + search_field = ft.TextField( + hint_text="ค้นหาบทความ", + prefix_icon=ft.Icons.SEARCH, + ) + + current_page = {'value':1} + + page_text = ft.Text(value=f"หน้า {current_page['value']}") + # สร้างปุ่ม + btn_prev = ft.OutlinedButton('ก่อนหน้า', disabled=True) + btn_next = ft.OutlinedButton('ถัดไป', disabled=True) + + async def load_articles(): + articles_column.controls.clear() + articles_column.controls.append(ft.ProgressRing()) + + btn_prev.disabled = True + btn_next.disabled = True + + page.update() + + try: + data = await asyncio.to_thread( + list_articles, + page=current_page['value'], + search=search_field.value, + token=state.access_token, + ) + + # อัปเดตสถานะปุ่มจากข้อมูล Backend + btn_prev.disabled = data.get('previous') is None + btn_next.disabled = data.get('next') is None + page_text.value = f"หน้า {current_page['value']}" + + articles_column.controls.clear() + for item in data.get('results', []): + articles_column.controls.append(article_card(page, item)) + + if not data.get('results'): + articles_column.controls.append(ft.Text("ไม่พบบทความ")) + + except Exception as e: + articles_column.controls.clear() + articles_column.controls.append(ft.Text(f"เกิดข้อผิดพลาด: {e}", color=ft.Colors.RED)) + btn_prev.disabled = current_page['value'] <= 1 + btn_next.disabled = True + + page.update() + + # Logic การเปลี่ยนหน้า + async def change_page(delta): + current_page['value'] += delta + await load_articles() + btn_prev.on_click = lambda e: asyncio.create_task(change_page(-1)) + btn_next.on_click = lambda e: asyncio.create_task(change_page(1)) + + def on_search(e): + current_page['value'] = 1 + asyncio.create_task(load_articles()) + + search_field.on_submit = on_search + + controls = ft.Column( + controls=[ + ft.Text('บทความ', size=24), + ft.Row([search_field]), + articles_column, + ft.Row( + controls=[btn_prev, page_text, btn_next], + alignment=ft.MainAxisAlignment.CENTER + ) + ], + expand=True, + ) + + asyncio.create_task(load_articles()) + return controls diff --git a/app/pages/courses.py b/app/pages/courses.py index 22a5f5c..8bbf334 100644 --- a/app/pages/courses.py +++ b/app/pages/courses.py @@ -1,6 +1,6 @@ import flet as ft -def courses_page(): +def courses_page(page: ft.Page): return ft.Column([ ft.Text("หลักสูตร", size=22), ft.Text("รายการหลักสูตร") diff --git a/app/pages/login.py b/app/pages/login.py index 2e30386..218aceb 100644 --- a/app/pages/login.py +++ b/app/pages/login.py @@ -1,7 +1,7 @@ import flet as ft import asyncio -from app.api import login, get_me +from app.api.auth import login, get_me from app.state import state def login_page(page: ft.Page, on_login_success): diff --git a/app/pages/my_courses.py b/app/pages/my_courses.py index 3955743..dd82056 100644 --- a/app/pages/my_courses.py +++ b/app/pages/my_courses.py @@ -1,6 +1,6 @@ import flet as ft -def my_courses_page(): +def my_courses_page(page: ft.Page): return ft.Column([ ft.Text('หลักสูตรของฉัน', size=22), ft.Text('ข้อมูลหลักสูตรที่ลงทะเบียน') diff --git a/app/pages/profile.py b/app/pages/profile.py index 36d1566..eecf65f 100644 --- a/app/pages/profile.py +++ b/app/pages/profile.py @@ -1,6 +1,6 @@ import flet as ft -def profile_page(): +def profile_page(page: ft.Page): return ft.Column([ ft.Text("ข้อมูลผู้ใช้งาน", size=22), ft.Text("รายละเอียดข้อมูลผู้ใช้งานจะมาอยู่ตรงนี้"), diff --git a/app/pages/progress.py b/app/pages/progress.py index 753b12b..c2d40ab 100644 --- a/app/pages/progress.py +++ b/app/pages/progress.py @@ -1,6 +1,6 @@ import flet as ft -def progress_page(): +def progress_page(page: ft.Page): return ft.Column([ ft.Text('ความก้าวหน้าของฉัน', size=22), ft.Text('รายละเอียดความก้าวหน้าของฉัน') diff --git a/main.py b/main.py index e0911a6..67f1610 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ import flet as ft from app.layout import main_layout +from app.pages.article_detail import article_detail_page from app.pages.login import login_page from app.pages.articles import articles_page from app.pages.courses import courses_page @@ -27,6 +28,7 @@ async def main(page: ft.Page): # ROUTES MAP routes = { "/articles": articles_page, + "/articles/{id}": article_detail_page, "/courses": courses_page, "/my_courses": my_courses_page, "/progress": progress_page, @@ -73,8 +75,16 @@ async def main(page: ft.Page): ) # เปลี่ยน content อย่างเดียว - if page.route in routes: - main_container.content = routes[page.route]() + # จัดการ Dynamic Route ก่อน เช่น ARTICLE DETAIL + if page.route.startswith("/articles/"): + try: + article_id = int(page.route.split('/')[-1]) + main_container.content = article_detail_page(page, article_id) + except ValueError: + main_container.content = ft.Text('Invalid article ID') + # จัดการ Static Routes ที่เหลือ + elif page.route in routes: + main_container.content = routes[page.route](page) else: main_container.content = ft.Text("404 NOT FOUND")