action slot
This commit is contained in:
@@ -5,6 +5,7 @@
|
||||
# ]
|
||||
# ///
|
||||
|
||||
from nicegui import events
|
||||
from nicegui import ui
|
||||
|
||||
type TableValue = str | int | float
|
||||
@@ -17,6 +18,12 @@ STATUS_COLORS = {
|
||||
}
|
||||
|
||||
COLUMNS = [
|
||||
{
|
||||
"name": "actions",
|
||||
"label": "Actions",
|
||||
"required": True,
|
||||
"align": "center",
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Product",
|
||||
@@ -47,6 +54,7 @@ COLUMNS = [
|
||||
"headerStyle": "width: 7rem",
|
||||
"style": "width: 7rem",
|
||||
":classes": "row => row.stock < 10 ? 'text-negative font-bold' : ''",
|
||||
":format": "value => value == null ? '' : `${value} units`",
|
||||
},
|
||||
{
|
||||
"name": "price",
|
||||
@@ -56,7 +64,7 @@ COLUMNS = [
|
||||
"align": "right",
|
||||
"headerStyle": "width: 8rem",
|
||||
"style": "width: 8rem",
|
||||
":format": "value => `$${value.toFixed(2)}`",
|
||||
":format": "value => value == null ? '' : `$${value.toFixed(2)}`",
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
@@ -68,15 +76,85 @@ COLUMNS = [
|
||||
"style": "width: 8rem",
|
||||
"colorByValue": STATUS_COLORS,
|
||||
},
|
||||
{
|
||||
"name": "updated_at",
|
||||
"label": "Updated",
|
||||
"field": "updated_at",
|
||||
"sortable": True,
|
||||
"align": "left",
|
||||
"headerStyle": "width: 13rem",
|
||||
"style": "width: 13rem",
|
||||
":sort": "(left, right) => Date.parse(left) - Date.parse(right)",
|
||||
":format": """(() => {
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
dateStyle: 'medium',
|
||||
timeStyle: 'short',
|
||||
timeZone: 'UTC',
|
||||
});
|
||||
return value => {
|
||||
if (!value) return '';
|
||||
const timestamp = Date.parse(value);
|
||||
return Number.isNaN(timestamp) ? 'Invalid date' : formatter.format(timestamp);
|
||||
};
|
||||
})()""",
|
||||
},
|
||||
]
|
||||
|
||||
ROWS: list[TableRow] = [
|
||||
{"id": 101, "name": "Desk lamp", "category": "Lighting", "stock": 7, "price": 42.5, "status": "Low"},
|
||||
{"id": 102, "name": "Task chair", "category": "Seating", "stock": 18, "price": 289.0, "status": "Ready"},
|
||||
{"id": 103, "name": "Monitor arm", "category": "Hardware", "stock": 0, "price": 119.95, "status": "Backorder"},
|
||||
{"id": 104, "name": "Cable tray", "category": "Hardware", "stock": 34, "price": 31.25, "status": "Ready"},
|
||||
{"id": 105, "name": "Side table", "category": "Furniture", "stock": 9, "price": 164.5, "status": "Low"},
|
||||
{"id": 106, "name": "Floor light", "category": "Lighting", "stock": 15, "price": 98.0, "status": "Ready"},
|
||||
{
|
||||
"id": 101,
|
||||
"name": "Desk lamp",
|
||||
"category": "Lighting",
|
||||
"stock": 7,
|
||||
"price": 42.5,
|
||||
"status": "Low",
|
||||
"updated_at": "2026-08-31T16:20:00Z",
|
||||
},
|
||||
{
|
||||
"id": 102,
|
||||
"name": "Task chair",
|
||||
"category": "Seating",
|
||||
"stock": 18,
|
||||
"price": 289.0,
|
||||
"status": "Ready",
|
||||
"updated_at": "2026-09-01T08:45:00Z",
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"name": "Monitor arm",
|
||||
"category": "Hardware",
|
||||
"stock": 0,
|
||||
"price": 119.95,
|
||||
"status": "Backorder",
|
||||
"updated_at": "2026-08-29T11:05:00Z",
|
||||
},
|
||||
{
|
||||
"id": 104,
|
||||
"name": "Cable tray",
|
||||
"category": "Hardware",
|
||||
"stock": 34,
|
||||
"price": 31.25,
|
||||
"status": "Ready",
|
||||
"updated_at": "2026-09-01T14:30:00Z",
|
||||
},
|
||||
{
|
||||
"id": 105,
|
||||
"name": "Side table",
|
||||
"category": "Furniture",
|
||||
"stock": 9,
|
||||
"price": 164.5,
|
||||
"status": "Low",
|
||||
"updated_at": "2026-08-30T19:15:00Z",
|
||||
},
|
||||
{
|
||||
"id": 106,
|
||||
"name": "Floor light",
|
||||
"category": "Lighting",
|
||||
"stock": 15,
|
||||
"price": 98.0,
|
||||
"status": "Ready",
|
||||
"updated_at": "2026-09-01T10:10:00Z",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -112,10 +190,10 @@ def render_table() -> ui.table:
|
||||
)
|
||||
)
|
||||
|
||||
with table.add_slot("top-left"), ui.row().classes("items-center gap-2"):
|
||||
ui.icon("inventory_2", size="sm").classes("text-primary")
|
||||
with table.add_slot("top-left"), ui.row(align_items="center").classes("gap-2"):
|
||||
ui.icon("inventory_2", size="sm", color="primary")
|
||||
ui.label("Inventory").classes("text-xl font-medium")
|
||||
ui.badge(str(len(ROWS)), color="grey-3").props("text-color=grey-9")
|
||||
ui.badge(str(len(ROWS)), color="grey-3", text_color="grey-9")
|
||||
|
||||
with table.add_slot("top-right"):
|
||||
ui.input(placeholder="Search inventory").props("dense outlined clearable debounce=250").bind_value_to(
|
||||
@@ -124,20 +202,36 @@ def render_table() -> ui.table:
|
||||
)
|
||||
|
||||
with table.add_slot("body-cell-status"), table.cell("status"):
|
||||
ui.badge().props(':label="props.value" :color="props.col.colorByValue[props.value] ?? \'grey\'" outline')
|
||||
ui.badge(outline=True).props(':label="props.value" :color="props.col.colorByValue[props.value] ?? \'grey\'"')
|
||||
|
||||
with table.add_slot("no-data"), ui.row().classes("w-full items-center justify-center gap-2 p-6 text-grey-7"):
|
||||
def open_product(event: events.GenericEventArguments) -> None:
|
||||
product = next((row for row in table.rows if row[table.row_key] == event.args), None)
|
||||
if product is None:
|
||||
ui.notify("Product no longer exists", type="negative")
|
||||
return
|
||||
ui.notify(f"Opening {product['name']}")
|
||||
|
||||
with (
|
||||
table.add_slot("body-cell-actions"),
|
||||
table.cell("actions"),
|
||||
ui.button(icon="open_in_new")
|
||||
.props("round flat size='sm'")
|
||||
.on(
|
||||
"click.stop",
|
||||
handler=open_product,
|
||||
js_handler="() => emit(props.key)",
|
||||
),
|
||||
):
|
||||
ui.tooltip("Open product")
|
||||
|
||||
with table.add_slot("no-data"), ui.row(align_items="center").classes("w-full justify-center gap-2 p-6 text-grey-7"):
|
||||
ui.icon("inventory_2", size="2em").props(":name=\"props.filter ? 'filter_alt_off' : 'inventory_2'\"")
|
||||
ui.element("span").props(':textContent="props.message"')
|
||||
|
||||
optional_columns = [column for column in table.columns if not column.get("required")]
|
||||
|
||||
def set_visible_columns(names: list[str]) -> None:
|
||||
visible = set(names)
|
||||
for column in optional_columns:
|
||||
hidden = column["name"] not in visible
|
||||
column["classes"] = "hidden" if hidden else ""
|
||||
column["headerClasses"] = "hidden" if hidden else "text-grey-8"
|
||||
table.props["visible-columns"] = names
|
||||
table.update()
|
||||
|
||||
ui.select(
|
||||
@@ -145,6 +239,7 @@ def render_table() -> ui.table:
|
||||
value=[column["name"] for column in optional_columns],
|
||||
label="Visible columns",
|
||||
multiple=True,
|
||||
clearable=True,
|
||||
on_change=lambda event: set_visible_columns(event.value),
|
||||
).props("outlined dense options-dense").classes("w-64")
|
||||
|
||||
@@ -152,7 +247,7 @@ def render_table() -> ui.table:
|
||||
|
||||
|
||||
if __name__ in {"__main__", "__mp_main__"}:
|
||||
with ui.column().classes("w-full items-center gap-4 p-4"):
|
||||
with ui.column(align_items="center").classes("w-full gap-4 p-4"):
|
||||
render_table()
|
||||
|
||||
ui.run(port=8888, reload=True)
|
||||
|
||||
Reference in New Issue
Block a user