import dash
from dash import Dash, html, Input, Output, State, callback, ALL, MATCH
import dash_mantine_components as dmc
from dash_iconify import DashIconify
app = Dash(__name__)
# Datos iniciales para tarjetas dinámicas
initial_cards = [
{"id": 1, "title": "Ventas", "value": 1250, "icon": "tabler:chart-line"},
{"id": 2, "title": "Usuarios", "value": 89, "icon": "tabler:users"},
{"id": 3, "title": "Pedidos", "value": 34, "icon": "tabler:shopping-cart"}
]
app.layout = dmc.MantineProvider([
dmc.Container([
dmc.Title("Dashboard Dinámico", order=1, mb="lg"),
# Botones de control
dmc.Group([
dmc.Button("Agregar Tarjeta", id="add-card-btn", leftSection=DashIconify(icon="tabler:plus")),
dmc.Button("Actualizar Datos", id="refresh-btn", leftSection=DashIconify(icon="tabler:refresh")),
], mb="xl"),
# Contenedor de tarjetas dinámicas
dmc.SimpleGrid(
id="cards-container",
cols=3,
children=[
dmc.Card([
dmc.Group([
DashIconify(
icon=card["icon"],
width=30,
id={"type": "card-icon", "index": card["id"]}
),
dmc.Stack([
dmc.Text(card["title"], size="sm", c="dimmed"),
dmc.Text(str(card["value"]), size="xl", fw=700, id={"type": "card-value", "index": card["id"]})
], gap="xs")
], justify="space-between"),
dmc.Button(
"Eliminar",
variant="light",
color="red",
size="xs",
id={"type": "delete-btn", "index": card["id"]},
mt="sm"
)
],
withBorder=True,
shadow="sm",
radius="md",
id={"type": "card", "index": card["id"]},
key=f"card-{card['id']}"
) for card in initial_cards
]
),
# Modal para agregar nueva tarjeta
dmc.Modal(
title="Nueva Tarjeta",
id="add-modal",
children=[
dmc.Stack([
dmc.TextInput(label="Título", id="new-title", placeholder="Ej: Ingresos"),
dmc.NumberInput(label="Valor inicial", id="new-value", value=0),
dmc.Select(
label="Icono",
id="new-icon",
data=[
{"value": "tabler:chart-bar", "label": "Gráfico de barras"},
{"value": "tabler:currency-dollar", "label": "Dinero"},
{"value": "tabler:trending-up", "label": "Tendencia"},
{"value": "tabler:star", "label": "Estrella"}
]
),
dmc.Group([
dmc.Button("Cancelar", variant="outline", id="cancel-btn"),
dmc.Button("Crear", id="create-btn")
], justify="flex-end")
])
]
)
], size="lg")
])
# Callback para abrir modal
@callback(
Output("add-modal", "opened"),
Input("add-card-btn", "n_clicks"),
Input("cancel-btn", "n_clicks"),
Input("create-btn", "n_clicks"),
prevent_initial_call=True
)
def toggle_modal(add_clicks, cancel_clicks, create_clicks):
ctx = dash.callback_context
if ctx.triggered_id == "add-card-btn":
return True
return False
# Callback principal para manejar tarjetas dinámicas
@callback(
Output("cards-container", "children"),
Input("create-btn", "n_clicks"),
Input({"type": "delete-btn", "index": ALL}, "n_clicks"),
Input("refresh-btn", "n_clicks"),
State("new-title", "value"),
State("new-value", "value"),
State("new-icon", "value"),
State("cards-container", "children"),
prevent_initial_call=True
)
def manage_cards(create_clicks, delete_clicks, refresh_clicks, title, value, icon, current_cards):
ctx = dash.callback_context
if not ctx.triggered:
return current_cards
trigger_id = ctx.triggered[0]["prop_id"]
# Crear nueva tarjeta
if "create-btn" in trigger_id and title and icon:
new_id = max([card["props"]["id"]["index"] for card in current_cards]) + 1
new_card = dmc.Card([
dmc.Group([
DashIconify(
icon=icon,
width=30,
id={"type": "card-icon", "index": new_id}
),
dmc.Stack([
dmc.Text(title, size="sm", c="dimmed"),
dmc.Text(str(value or 0), size="xl", fw=700, id={"type": "card-value", "index": new_id})
], gap="xs")
], justify="space-between"),
dmc.Button(
"Eliminar",
variant="light",
color="red",
size="xs",
id={"type": "delete-btn", "index": new_id},
mt="sm"
)
],
withBorder=True,
shadow="sm",
radius="md",
id={"type": "card", "index": new_id},
key=f"card-{new_id}"
)
return current_cards + [new_card]
# Eliminar tarjeta
elif "delete-btn" in trigger_id:
clicked_index = eval(trigger_id.split('.')[0])["index"]
return [card for card in current_cards if card["props"]["id"]["index"] != clicked_index]
# Actualizar valores (simulado)
elif "refresh-btn" in trigger_id:
import random
updated_cards = []
for card in current_cards:
# Crear una copia de la tarjeta con valor actualizado
card_copy = card.copy()
card_id = card["props"]["id"]["index"]
# Buscar y actualizar el componente de valor
for child in card_copy["props"]["children"]:
if hasattr(child, "props") and "children" in child["props"]:
for subchild in child["props"]["children"]:
if (hasattr(subchild, "props") and
"children" in subchild["props"] and
isinstance(subchild["props"]["children"], list)):
for item in subchild["props"]["children"]:
if (hasattr(item, "props") and
"id" in item["props"] and
item["props"]["id"]["type"] == "card-value"):
# Actualizar el valor con uno aleatorio
item["props"]["children"] = str(random.randint(50, 2000))
updated_cards.append(card_copy)
return updated_cards
return current_cards
if __name__ == "__main__":
app.run_server(debug=True)