First Working version. I can add table
This commit is contained in:
0
src/pages/__init__.py
Normal file
0
src/pages/__init__.py
Normal file
210
src/pages/admin.py
Normal file
210
src/pages/admin.py
Normal file
@@ -0,0 +1,210 @@
|
||||
from fasthtml.common import *
|
||||
|
||||
from core.user_dao import UserDAO
|
||||
|
||||
|
||||
def admin_dashboard():
|
||||
"""
|
||||
Create the admin dashboard page.
|
||||
|
||||
Returns:
|
||||
Components representing the admin dashboard
|
||||
"""
|
||||
return Div(
|
||||
# Page header
|
||||
H1("Admin Dashboard", cls="text-3xl font-bold text-gray-800 mb-6"),
|
||||
|
||||
# Admin menu
|
||||
Div(
|
||||
A(
|
||||
Div(
|
||||
Div(
|
||||
"Users",
|
||||
cls="text-xl font-semibold mb-2"
|
||||
),
|
||||
P("Manage user accounts, set admin privileges", cls="text-sm text-gray-600"),
|
||||
cls="p-4"
|
||||
),
|
||||
href="/admin/users",
|
||||
cls="block bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 mb-4"
|
||||
),
|
||||
|
||||
A(
|
||||
Div(
|
||||
Div(
|
||||
"Title Generation History",
|
||||
cls="text-xl font-semibold mb-2"
|
||||
),
|
||||
P("View all users' title generation history", cls="text-sm text-gray-600"),
|
||||
cls="p-4"
|
||||
),
|
||||
href="/admin/history",
|
||||
cls="block bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow duration-200 mb-4"
|
||||
),
|
||||
|
||||
cls="max-w-2xl mx-auto"
|
||||
)
|
||||
)
|
||||
|
||||
def admin_users_page(page=1, error_message=None, success_message=None):
|
||||
"""
|
||||
Create the admin users management page.
|
||||
|
||||
Args:
|
||||
page: Current page number
|
||||
error_message: Optional error message
|
||||
success_message: Optional success message
|
||||
|
||||
Returns:
|
||||
Components representing the admin users page
|
||||
"""
|
||||
# Get users with pagination
|
||||
limit = 10
|
||||
offset = (page - 1) * limit
|
||||
users = UserDAO.get_all_users(limit=limit, offset=offset)
|
||||
|
||||
# Create message alert if needed
|
||||
message_alert = None
|
||||
if error_message:
|
||||
message_alert = Div(
|
||||
P(error_message, cls="text-sm"),
|
||||
cls="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4"
|
||||
)
|
||||
elif success_message:
|
||||
message_alert = Div(
|
||||
P(success_message, cls="text-sm"),
|
||||
cls="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded mb-4"
|
||||
)
|
||||
|
||||
# Create user rows
|
||||
user_rows = []
|
||||
for user in users:
|
||||
# Format dates
|
||||
created_at = user.get('created_at', 'N/A')
|
||||
if created_at and created_at != 'N/A':
|
||||
created_at = created_at.split('T')[0] # Simple date format
|
||||
|
||||
last_login = user.get('last_login', 'Never')
|
||||
if last_login and last_login != 'Never':
|
||||
last_login = last_login.split('T')[0] # Simple date format
|
||||
|
||||
# Create user row
|
||||
user_rows.append(
|
||||
Tr(
|
||||
Td(user['username'], cls="px-6 py-4 whitespace-nowrap"),
|
||||
Td(user['email'], cls="px-6 py-4 whitespace-nowrap"),
|
||||
Td(
|
||||
Span(
|
||||
"GitHub" if user.get('is_github_user') else "Email",
|
||||
cls=f"px-2 py-1 text-xs rounded-full {'bg-purple-200 text-purple-800' if user.get('is_github_user') else 'bg-blue-200 text-blue-800'}"
|
||||
),
|
||||
cls="px-6 py-4 whitespace-nowrap"
|
||||
),
|
||||
Td(created_at, cls="px-6 py-4 whitespace-nowrap"),
|
||||
Td(last_login, cls="px-6 py-4 whitespace-nowrap"),
|
||||
Td(
|
||||
Span(
|
||||
"Admin" if user.get('is_admin') else "User",
|
||||
cls=f"px-2 py-1 text-xs rounded-full {'bg-red-200 text-red-800' if user.get('is_admin') else 'bg-gray-200 text-gray-800'}"
|
||||
),
|
||||
cls="px-6 py-4 whitespace-nowrap"
|
||||
),
|
||||
Td(
|
||||
Div(
|
||||
# Toggle admin status
|
||||
Form(
|
||||
Button(
|
||||
"Remove Admin" if user.get('is_admin') else "Make Admin",
|
||||
type="submit",
|
||||
cls=f"{'bg-gray-500 hover:bg-gray-600' if user.get('is_admin') else 'bg-blue-500 hover:bg-blue-600'} text-white text-xs py-1 px-2 rounded mr-2"
|
||||
),
|
||||
action=f"/admin/users/{user['id']}/{'remove-admin' if user.get('is_admin') else 'make-admin'}",
|
||||
method="post",
|
||||
cls="inline"
|
||||
),
|
||||
|
||||
# Delete user
|
||||
Form(
|
||||
Button(
|
||||
"Delete",
|
||||
type="submit",
|
||||
cls="bg-red-500 hover:bg-red-600 text-white text-xs py-1 px-2 rounded"
|
||||
),
|
||||
action=f"/admin/users/{user['id']}/delete",
|
||||
method="post",
|
||||
cls="inline"
|
||||
),
|
||||
|
||||
cls="flex"
|
||||
),
|
||||
cls="px-6 py-4 whitespace-nowrap"
|
||||
),
|
||||
cls="bg-white border-b"
|
||||
)
|
||||
)
|
||||
|
||||
# Build pagination controls
|
||||
current_page = page
|
||||
pagination = Div(
|
||||
Div(
|
||||
A("← Previous",
|
||||
href=f"/admin/users?page={current_page - 1}" if current_page > 1 else "#",
|
||||
cls=f"px-4 py-2 rounded {'bg-blue-600 text-white' if current_page > 1 else 'bg-gray-200 text-gray-500 cursor-default'}"),
|
||||
Span(f"Page {current_page}",
|
||||
cls="px-4 py-2"),
|
||||
A("Next →",
|
||||
href=f"/admin/users?page={current_page + 1}" if len(users) == limit else "#",
|
||||
cls=f"px-4 py-2 rounded {'bg-blue-600 text-white' if len(users) == limit else 'bg-gray-200 text-gray-500 cursor-default'}"),
|
||||
cls="flex items-center justify-center space-x-2"
|
||||
),
|
||||
cls="mt-6"
|
||||
)
|
||||
|
||||
return Div(
|
||||
# Breadcrumb navigation
|
||||
Div(
|
||||
A("Admin Dashboard", href="/admin", cls="text-blue-600 hover:underline"),
|
||||
Span(" / ", cls="text-gray-500"),
|
||||
Span("Users", cls="font-semibold"),
|
||||
cls="mb-4 text-sm"
|
||||
),
|
||||
|
||||
# Page header
|
||||
H1("User Management", cls="text-3xl font-bold text-gray-800 mb-6"),
|
||||
|
||||
# Message alert
|
||||
message_alert if message_alert else "",
|
||||
|
||||
# Users table
|
||||
Div(
|
||||
Table(
|
||||
Thead(
|
||||
Tr(
|
||||
Th("Username", cls="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"),
|
||||
Th("Email", cls="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"),
|
||||
Th("Auth Type", cls="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"),
|
||||
Th("Created", cls="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"),
|
||||
Th("Last Login", cls="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"),
|
||||
Th("Role", cls="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"),
|
||||
Th("Actions", cls="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"),
|
||||
cls="bg-gray-50"
|
||||
)
|
||||
),
|
||||
Tbody(
|
||||
*user_rows if user_rows else [
|
||||
Tr(
|
||||
Td("No users found", colspan="7", cls="px-6 py-4 text-center text-gray-500 italic")
|
||||
)
|
||||
]
|
||||
),
|
||||
cls="min-w-full divide-y divide-gray-200"
|
||||
),
|
||||
cls="bg-white shadow overflow-x-auto rounded-lg"
|
||||
),
|
||||
|
||||
# Pagination
|
||||
pagination,
|
||||
|
||||
cls="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"
|
||||
)
|
||||
|
||||
220
src/pages/admin_import_settings.py
Normal file
220
src/pages/admin_import_settings.py
Normal file
@@ -0,0 +1,220 @@
|
||||
import dataclasses
|
||||
import uuid
|
||||
from io import BytesIO
|
||||
|
||||
import pandas as pd
|
||||
from fasthtml.components import *
|
||||
from fasthtml.fastapp import fast_app
|
||||
from starlette.datastructures import UploadFile
|
||||
|
||||
from components.datagrid.DataGrid import DataGrid, DG_FILTER_INPUT, DG_TABLE_FOOTER, DG_COLUMNS, DG_DATATYPE_STRING, \
|
||||
DG_DATATYPE_BOOL, DG_READ_ONLY
|
||||
from components.datagrid.constants import DG_ROWS_INDEXES
|
||||
from core.settings_management import SettingsManager
|
||||
from core.utils import make_html_id, make_column_id
|
||||
|
||||
ID_PREFIX = "import_settings"
|
||||
|
||||
import_settings_app, rt = fast_app()
|
||||
_instances = {}
|
||||
|
||||
IMPORT_SETTINGS_PATH = "import-settings"
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class ColumnDef:
|
||||
index: int
|
||||
title: str
|
||||
header: str
|
||||
position: str
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Config:
|
||||
type: str = None
|
||||
file_name: str = None
|
||||
sheet_name: str = None
|
||||
header_row: int = None
|
||||
columns: list[ColumnDef] = dataclasses.field(default_factory=list)
|
||||
|
||||
|
||||
class AdminImportSettings:
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
id_to_use = f"{ID_PREFIX}-{make_html_id(kwargs.get('id', None))}"
|
||||
if id_to_use in _instances:
|
||||
return _instances[id_to_use]
|
||||
return super().__new__(cls)
|
||||
|
||||
def __init__(self, settings_manager: SettingsManager, config: Config = None, /, id=None):
|
||||
if not hasattr(self, "_initialized"):
|
||||
self._initialized = True
|
||||
self._id = f"{ID_PREFIX}-{make_html_id(id) if id else uuid.uuid4().hex}"
|
||||
_instances[self._id] = self
|
||||
|
||||
self.settings_manager = settings_manager
|
||||
self.config = config or Config()
|
||||
self.grid_settings = {
|
||||
DG_COLUMNS: {
|
||||
"column_id": {
|
||||
"index": 1,
|
||||
"title": "Column Id",
|
||||
"type": DG_DATATYPE_STRING,
|
||||
DG_READ_ONLY: False
|
||||
},
|
||||
"column_header": {
|
||||
"index": 2,
|
||||
"title": "Column Header",
|
||||
"type": DG_DATATYPE_STRING,
|
||||
},
|
||||
"is_amount": {
|
||||
"index": 3,
|
||||
"title": "Amount",
|
||||
"type": DG_DATATYPE_BOOL,
|
||||
DG_READ_ONLY: False
|
||||
},
|
||||
},
|
||||
DG_FILTER_INPUT: False,
|
||||
DG_TABLE_FOOTER: False,
|
||||
# DG_COLUMNS_REORDERING: False,
|
||||
DG_ROWS_INDEXES: True,
|
||||
}
|
||||
self.datagrid = DataGrid(grid_settings=self.grid_settings)
|
||||
self.content = None
|
||||
self.sheet_names = None
|
||||
|
||||
def redraw(self):
|
||||
return (
|
||||
self._make_grid_component(),
|
||||
self._make_select_sheet_name_component(sheet_names=self.sheet_names, selected=self.config.sheet_name, oob=True),
|
||||
self._make_header_row_selection(header=self.config.header_row, oob=True),
|
||||
)
|
||||
|
||||
def _make_excel_upload_component(self, oob=False):
|
||||
return Input(type='file',
|
||||
name='file',
|
||||
hx_post=f"{IMPORT_SETTINGS_PATH}/upload",
|
||||
hx_target=f"#dg_{self._id}", # select sheet_name
|
||||
hx_encoding='multipart/form-data',
|
||||
hx_swap="outerHTML",
|
||||
hx_vals=f'{{"_id": "{self._id}"}}',
|
||||
id=f"fu_{self._id}", # fu stands for 'file upload'
|
||||
hx_swap_oob='true' if oob else None,
|
||||
cls="file-input file-input-bordered file-input-sm w-full",
|
||||
)
|
||||
|
||||
def _make_select_sheet_name_component(self, sheet_names=None, selected=None, oob=False):
|
||||
options = [Option("No file selected", selected=True, disabled=True)] if sheet_names is None else \
|
||||
[Option(
|
||||
name,
|
||||
selected=True if name == selected else None,
|
||||
) for name in sheet_names]
|
||||
|
||||
return Select(
|
||||
*options,
|
||||
id=f"s_{self._id}",
|
||||
name="sheet_name",
|
||||
hx_post=f"{IMPORT_SETTINGS_PATH}/get-columns",
|
||||
hx_include=f"#hr_{self._id}",
|
||||
hx_target=f"#dg_{self._id}",
|
||||
hx_vals=f'{{"_id": "{self._id}"}}',
|
||||
hx_swap_oob='true' if oob else None,
|
||||
cls="select select-bordered select-sm w-full"
|
||||
)
|
||||
|
||||
def _make_header_row_selection(self, header=None, oob=False):
|
||||
return Input(type="text",
|
||||
name="header_row",
|
||||
id=f"hr_{self._id}",
|
||||
placeholder="Header row",
|
||||
value=f"{header}",
|
||||
hx_post=f"{IMPORT_SETTINGS_PATH}/get-columns",
|
||||
hx_include=f"#s_{self._id}",
|
||||
hx_target=f"#dg_{self._id}",
|
||||
hx_vals=f'{{"_id": "{self._id}"}}',
|
||||
hx_swap_oob='true' if oob else None,
|
||||
cls="input input-bordered input-sm w-full",
|
||||
)
|
||||
|
||||
def _make_grid_component(self, oob=False):
|
||||
return Div(
|
||||
self.datagrid,
|
||||
id=f"dg_{self._id}",
|
||||
hx_swap_oob='true' if oob else None,
|
||||
)
|
||||
|
||||
def _make_use_columns_letters(self, oob=False):
|
||||
return Label(
|
||||
Span("Use columns letters", cls="label-text"),
|
||||
Input(type="checkbox", cls="checkbox checkbox-sm", ),
|
||||
cls="label cursor-pointer", )
|
||||
|
||||
def get_columns_def(self, columns):
|
||||
res = []
|
||||
_mapping = {
|
||||
"Column Id": lambda c: make_column_id(str(c).strip()),
|
||||
"Column Header": lambda c: c,
|
||||
"Amount": lambda c: False,
|
||||
}
|
||||
for column in columns:
|
||||
conf = {}
|
||||
res.append(conf)
|
||||
for col_id, col_conf in self.grid_settings[DG_COLUMNS].items():
|
||||
col_title = col_conf["title"]
|
||||
conf[col_title] = _mapping[col_title](column)
|
||||
return res
|
||||
|
||||
def on_file_uploaded(self, file_name, content):
|
||||
if content is None:
|
||||
return None
|
||||
|
||||
self.content = content
|
||||
excel_file = pd.ExcelFile(BytesIO(self.content))
|
||||
sheet_names = excel_file.sheet_names
|
||||
self.sheet_names = sheet_names
|
||||
self.config.file_name = file_name
|
||||
self.config.sheet_name = sheet_names[0]
|
||||
self.config.header_row = 0
|
||||
self.on_excel_conf_changed(self.config.sheet_name, self.config.header_row)
|
||||
|
||||
def on_excel_conf_changed(self, sheet_name, header):
|
||||
self.config.sheet_name = sheet_name
|
||||
self.config.header_row = header
|
||||
if self.content is not None:
|
||||
df = pd.read_excel(BytesIO(self.content), sheet_name=sheet_name, header=header)
|
||||
columns_def = self.get_columns_def(df.columns)
|
||||
new_content = pd.DataFrame(columns_def)
|
||||
self.datagrid.import_dataframe(new_content, reset=False)
|
||||
|
||||
def __ft__(self):
|
||||
return Div(
|
||||
Div(self._make_excel_upload_component(), cls="col-span-2"),
|
||||
Div(self._make_select_sheet_name_component(), cls="col-span-2"),
|
||||
Div(self._make_header_row_selection(), cls="col-span-1"),
|
||||
Div(self._make_use_columns_letters(), cls="col-span-1"),
|
||||
cls="grid grid-cols-5 gap-2",
|
||||
), Div(self._make_grid_component())
|
||||
|
||||
|
||||
@rt("/upload")
|
||||
async def post(session, _id: str, file: UploadFile):
|
||||
try:
|
||||
instance = _instances[_id]
|
||||
content = await file.read()
|
||||
|
||||
instance.on_file_uploaded(file.filename, content)
|
||||
return instance.redraw()
|
||||
except ValueError as error:
|
||||
return Div(f"{error}", role="alert", cls="alert alert-error")
|
||||
|
||||
|
||||
@rt("/get-columns")
|
||||
def post(session, _id: str, sheet_name: str, header_row: str):
|
||||
print(f"sheet_name={sheet_name}, header_row={header_row}")
|
||||
try:
|
||||
instance = _instances[_id]
|
||||
header = int(header_row) if header_row is not None else 0
|
||||
instance.on_excel_conf_changed(sheet_name, header)
|
||||
return instance.redraw()
|
||||
except Exception as error:
|
||||
return Div(f"{error}", role="alert", cls="alert alert-error")
|
||||
15
src/pages/another_grid.py
Normal file
15
src/pages/another_grid.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import pandas as pd
|
||||
|
||||
from components.datagrid.DataGrid import DataGrid
|
||||
|
||||
data = {
|
||||
'Name': ['Kodjo', 'Kokoe', 'Aba', 'Koffi'],
|
||||
'Age': [49, 51, 46, 51],
|
||||
'City': ['Rosny', 'Nangis', 'Rosny', 'Abidjan']
|
||||
}
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
|
||||
def get_datagrid2():
|
||||
return DataGrid(df, id="another grid")
|
||||
98
src/pages/basic_test.py
Normal file
98
src/pages/basic_test.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from fasthtml.common import *
|
||||
|
||||
from components.datagrid.icons import icon_filter_regular, icon_dismiss_regular
|
||||
|
||||
basic_test_app, rt = fast_app()
|
||||
BASIC_TEST_PATH = "basic_test"
|
||||
|
||||
# def get_basic_test(): return Div(P('Hello World!'), hx_get=f"{BASIC_TEST_PATH}/change", hx_swap="outerHTML")
|
||||
#
|
||||
#
|
||||
# @rt('/change')
|
||||
# def get(): return Div(P('Nice to be here!'), hx_get=f"{BASIC_TEST_PATH}/change_again", hx_swap="outerHTML")
|
||||
#
|
||||
#
|
||||
# @rt('/change_again')
|
||||
# def get(): return Div(P('I changed again'), hx_get=f"{BASIC_TEST_PATH}/", hx_swap="outerHTML")
|
||||
|
||||
# def get_basic_test():
|
||||
# icons = {"up": icon_chevron_sort_up,
|
||||
# "down": icon_chevron_sort_down,
|
||||
# "sort": icon_chevron_sort,
|
||||
# "filter": icon_filter}
|
||||
# return Div(
|
||||
# Table(
|
||||
# Thead(),
|
||||
# Tbody(
|
||||
# *[
|
||||
# Tr(
|
||||
# Td(name), Td(Div(icon, cls="icon-24")),
|
||||
# ) for name, icon in icons.items()
|
||||
# ]
|
||||
# )
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# def get_basic_test():
|
||||
# return Div("Get Some HTML, Including A Value in the Request", hx_post="/example", hx_vals='{"myVal": "My Value"}')
|
||||
|
||||
after_request_attr = {"hx-on::after-request": f"console.log('after-request');"}
|
||||
|
||||
self_id = 'my_id'
|
||||
|
||||
|
||||
def def_get_filter(oob=False):
|
||||
def _inner_get_filter():
|
||||
pass
|
||||
|
||||
return Div(
|
||||
Label(Div(icon_filter_regular, cls="icon-24"),
|
||||
Input(name='f',
|
||||
placeholder="Filter...",
|
||||
hx_post=f"/{BASIC_TEST_PATH}/filter",
|
||||
hx_trigger="keyup changed delay:300ms",
|
||||
hx_target=f"#tb_{self_id}",
|
||||
hx_vals=f'{{"g_id": "{self_id}", "c_id":"{BASIC_TEST_PATH}"}}',
|
||||
hx_swap_oob='true' if oob else None,
|
||||
**after_request_attr,
|
||||
),
|
||||
cls="input input-bordered input-sm flex items-center gap-2"
|
||||
),
|
||||
id=f"f_{self_id}",
|
||||
hx_swap_oob='true' if oob else None,
|
||||
)
|
||||
|
||||
|
||||
def get_component(oob=False):
|
||||
return Div(
|
||||
def_get_filter(),
|
||||
Div(icon_dismiss_regular,
|
||||
cls="icon-24 my-auto icon-btn ml-2",
|
||||
hx_post=f"{BASIC_TEST_PATH}/reset_filter",
|
||||
hx_trigger="click",
|
||||
hx_target=f"#tb_{self_id}",
|
||||
hx_vals=f'{{"g_id": "{self_id}", "c_id":"{BASIC_TEST_PATH}"}}',
|
||||
**after_request_attr),
|
||||
cls="flex mb-2",
|
||||
id=f"fa_{self_id}", # fa stands for 'filter all'
|
||||
hx_swap_oob='true' if oob else None,
|
||||
)
|
||||
|
||||
|
||||
def get_basic_test():
|
||||
return get_component(), Div(id=f"tb_{self_id}")
|
||||
|
||||
|
||||
@rt(f"/filter")
|
||||
def post(f: str):
|
||||
return Div(f)
|
||||
|
||||
|
||||
@rt(f"/reset_filter")
|
||||
def post():
|
||||
res = (Div(f"You reset",
|
||||
hx_swap_oob="true",
|
||||
id=f"tb_{self_id}"
|
||||
),
|
||||
def_get_filter(oob=True))
|
||||
return res,
|
||||
134
src/pages/home.py
Normal file
134
src/pages/home.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from fasthtml.common import *
|
||||
import config
|
||||
from auth.auth_manager import AuthManager
|
||||
|
||||
def home(session=None):
|
||||
"""
|
||||
Defines the home page content.
|
||||
|
||||
Args:
|
||||
session: The session object for auth status
|
||||
|
||||
Returns:
|
||||
Components representing the home page content
|
||||
"""
|
||||
# Check if user is authenticated
|
||||
is_authenticated = AuthManager.is_authenticated(session) if session else False
|
||||
is_admin = AuthManager.is_admin(session) if session else False
|
||||
username = session.get("username", "") if session else ""
|
||||
|
||||
# Hero content varies based on authentication
|
||||
if is_authenticated:
|
||||
hero_content = Div(
|
||||
H1(f"Welcome back, {username}!",
|
||||
cls="text-4xl font-bold text-center text-gray-800 mb-4"),
|
||||
P("Continue creating engaging titles for your content with AI assistance.",
|
||||
cls="text-xl text-center text-gray-600 mb-6"),
|
||||
Div(
|
||||
A("Generate New Titles",
|
||||
href="/title-generator",
|
||||
cls="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-3"),
|
||||
A("View My History",
|
||||
href="/history",
|
||||
cls="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded"),
|
||||
*([A("Admin Dashboard",
|
||||
href="/admin",
|
||||
cls="ml-3 bg-purple-600 hover:bg-purple-700 text-white font-bold py-2 px-4 rounded")] if is_admin else []),
|
||||
cls="flex justify-center flex-wrap gap-y-2"
|
||||
),
|
||||
cls="py-12"
|
||||
)
|
||||
else:
|
||||
hero_content = Div(
|
||||
H1(config.APP_NAME,
|
||||
cls="text-4xl font-bold text-center text-gray-800 mb-4"),
|
||||
P("Create engaging titles for your content with AI assistance.",
|
||||
cls="text-xl text-center text-gray-600 mb-6"),
|
||||
Div(
|
||||
A("Sign In",
|
||||
href="/login",
|
||||
cls="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded mr-3"),
|
||||
A("Register",
|
||||
href="/register",
|
||||
cls="bg-gray-200 hover:bg-gray-300 text-gray-800 font-bold py-2 px-4 rounded"),
|
||||
cls="flex justify-center"
|
||||
),
|
||||
cls="py-12"
|
||||
)
|
||||
|
||||
return Div(
|
||||
# Hero section with conditional content
|
||||
hero_content,
|
||||
|
||||
# Features section
|
||||
Div(
|
||||
H2("Features", cls="text-3xl font-bold text-center mb-8"),
|
||||
Div(
|
||||
# Feature 1
|
||||
Div(
|
||||
H3("Platform-Specific", cls="text-xl font-semibold mb-2"),
|
||||
P("Generate titles optimized for blogs, YouTube, social media, and more.",
|
||||
cls="text-gray-600"),
|
||||
cls="bg-white p-6 rounded-lg shadow-md"
|
||||
),
|
||||
# Feature 2
|
||||
Div(
|
||||
H3("Multiple Styles", cls="text-xl font-semibold mb-2"),
|
||||
P("Choose from professional, casual, clickbait, or informative styles.",
|
||||
cls="text-gray-600"),
|
||||
cls="bg-white p-6 rounded-lg shadow-md"
|
||||
),
|
||||
# Feature 3
|
||||
Div(
|
||||
H3("AI-Powered", cls="text-xl font-semibold mb-2"),
|
||||
P("Utilizes advanced AI models to craft engaging, relevant titles.",
|
||||
cls="text-gray-600"),
|
||||
cls="bg-white p-6 rounded-lg shadow-md"
|
||||
),
|
||||
cls="grid grid-cols-1 md:grid-cols-3 gap-6"
|
||||
),
|
||||
cls="py-8"
|
||||
),
|
||||
|
||||
# How it works section
|
||||
Div(
|
||||
H2("How It Works", cls="text-3xl font-bold text-center mb-8"),
|
||||
Div(
|
||||
# Step 1
|
||||
Div(
|
||||
Div(
|
||||
"1",
|
||||
cls="flex items-center justify-center bg-blue-600 text-white text-xl font-bold rounded-full w-10 h-10 mb-4"
|
||||
),
|
||||
H3("Enter Your Topic", cls="text-xl font-semibold mb-2"),
|
||||
P("Describe what your content is about in detail.",
|
||||
cls="text-gray-600"),
|
||||
cls="bg-white p-6 rounded-lg shadow-md"
|
||||
),
|
||||
# Step 2
|
||||
Div(
|
||||
Div(
|
||||
"2",
|
||||
cls="flex items-center justify-center bg-blue-600 text-white text-xl font-bold rounded-full w-10 h-10 mb-4"
|
||||
),
|
||||
H3("Choose Settings", cls="text-xl font-semibold mb-2"),
|
||||
P("Select the platform and style that matches your needs.",
|
||||
cls="text-gray-600"),
|
||||
cls="bg-white p-6 rounded-lg shadow-md"
|
||||
),
|
||||
# Step 3
|
||||
Div(
|
||||
Div(
|
||||
"3",
|
||||
cls="flex items-center justify-center bg-blue-600 text-white text-xl font-bold rounded-full w-10 h-10 mb-4"
|
||||
),
|
||||
H3("Get Results", cls="text-xl font-semibold mb-2"),
|
||||
P("Review multiple title options and choose your favorite.",
|
||||
cls="text-gray-600"),
|
||||
cls="bg-white p-6 rounded-lg shadow-md"
|
||||
),
|
||||
cls="grid grid-cols-1 md:grid-cols-3 gap-6"
|
||||
),
|
||||
cls="py-8"
|
||||
)
|
||||
)
|
||||
67
src/pages/testing_datagrid.py
Normal file
67
src/pages/testing_datagrid.py
Normal file
@@ -0,0 +1,67 @@
|
||||
import pandas as pd
|
||||
|
||||
from components.datagrid.DataGrid import DataGrid, DG_COLUMNS, DG_READ_ONLY, DG_AGGREGATE_FILTERED_SUM, VISIBLE_KEY
|
||||
from components.datagrid.constants import DG_ROWS_INDEXES
|
||||
|
||||
data = {
|
||||
'Name': ['Alice', 'Bob', 'Charlie', 'David', "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
|
||||
'Age': [25, 30, 35, 40, 50],
|
||||
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston', "bbbbbbbbbbbbbbbbbbbbbbbbbbbb"],
|
||||
'Boolean': [True, False, False, True, False],
|
||||
'Choice': ["Bool", "Number", "Amount", None, "Number"]
|
||||
}
|
||||
|
||||
df = pd.DataFrame(data)
|
||||
|
||||
grid_settings = {
|
||||
# DG_READ_ONLY: False,
|
||||
# DG_SELECTION_MODE: DG_SELECTION_MODE_ROW,
|
||||
# DG_FILTER_INPUT: False,
|
||||
# DG_TABLE_HEADER: False,
|
||||
DG_ROWS_INDEXES: True,
|
||||
DG_COLUMNS: {
|
||||
"name": {
|
||||
"index": 0,
|
||||
"title": "Name",
|
||||
# "type": "list",
|
||||
"values": ["Alice", "Bob", "Charlie", "David"],
|
||||
DG_READ_ONLY: False,
|
||||
# "width": "150px",
|
||||
},
|
||||
"age": {
|
||||
"index": 1,
|
||||
"title": "Age",
|
||||
"type": "number",
|
||||
# "width": "100px",
|
||||
VISIBLE_KEY: False,
|
||||
"agg_func": DG_AGGREGATE_FILTERED_SUM,
|
||||
# "agg_func_2": DG_AGGREGATE_SUM,
|
||||
},
|
||||
"city": {
|
||||
"index": 2,
|
||||
"title": "City",
|
||||
"type": "list",
|
||||
"values": ["New York", "Los Angeles", "Chicago", "Houston"],
|
||||
DG_READ_ONLY: False,
|
||||
},
|
||||
"boolean": {
|
||||
"index": 4,
|
||||
"title": "Boolean",
|
||||
"type": "bool",
|
||||
# "width": "100px",
|
||||
DG_READ_ONLY: False,
|
||||
},
|
||||
"choice": {
|
||||
"index": 3,
|
||||
"title": "Choice",
|
||||
"type": "choice",
|
||||
"values": ["Bool", "Number", "Amount"],
|
||||
DG_READ_ONLY: False,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_datagrid():
|
||||
dg = DataGrid(df, grid_settings=grid_settings, id="testing_datagrid0")
|
||||
return dg.mk_excel_upload_component(), dg
|
||||
16
src/pages/testing_restore_state.py
Normal file
16
src/pages/testing_restore_state.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from fasthtml.common import *
|
||||
|
||||
|
||||
def testing_restore_state():
|
||||
return Div(
|
||||
Input(name='f', id=f"input_with_id", placeholder="Input with id"),
|
||||
Input(name='f', placeholder="Input without id"),
|
||||
|
||||
Input(type='checkbox', id="checkbox_with_id", checked='checked', cls='checkbox'),
|
||||
Input(type='checkbox', checked='checked', cls='checkbox'),
|
||||
|
||||
Input(type='radio', id="radio_with_id_1", name='radio-1', cls='radio'),
|
||||
Input(type='radio', id="radio_with_id_2", name='radio-1', cls='radio'),
|
||||
Input(type='radio', id="radio_with_id_3", name='radio-1', cls='radio'),
|
||||
|
||||
)
|
||||
Reference in New Issue
Block a user