Initial commit: Ballet Production Suite ERP/CRM foundation

This commit is contained in:
2026-01-24 13:28:54 +01:00
commit 01ae4a09ff
10 changed files with 568 additions and 0 deletions

141
frontend/app.py Normal file
View File

@@ -0,0 +1,141 @@
from nicegui import ui
import api_client
from pages.logistics import render_logistics_page
# Define the layout
@ui.page('/')
def main_page():
with ui.header().classes('items-center justify-between'):
ui.label('Suite de Producción de Ballet').classes('text-2xl font-bold')
with ui.row():
ui.button('Cerrar Sesión', icon='logout', on_click=lambda: ui.notify('Sesión cerrada'))
with ui.left_drawer(value=True).classes('bg-slate-100'):
with ui.column().classes('w-full gap-2 p-4'):
ui.button('Panel de Control', icon='dashboard', on_click=lambda: content.set_content('Dashboard')).classes('w-full justify-start')
ui.button('Productos', icon='inventory_2', on_click=lambda: content.set_content('Productos')).classes('w-full justify-start')
ui.button('CRM y Pedidos', icon='people', on_click=lambda: content.set_content('CRM')).classes('w-full justify-start')
ui.button('Producción', icon='factory', on_click=lambda: content.set_content('Producción')).classes('w-full justify-start')
ui.button('Inventario', icon='category', on_click=lambda: content.set_content('Inventario')).classes('w-full justify-start')
ui.button('Logística', icon='local_shipping', on_click=lambda: content.set_content('Logística')).classes('w-full justify-start')
with ui.column().classes('w-full p-8') as main_content:
ui.label('Bienvenido al Panel de Control de Ballet Atelier').classes('text-3xl')
ui.markdown('''
Gestione su negocio de ropa de ballet de manera eficiente:
- **Productos**: Seguimiento de sus más de 100 modelos y materiales.
- **CRM**: Gestione clientes y sus medidas únicas.
- **Producción**: Supervise los pedidos a través del flujo del taller.
- **Inventario**: Seguimiento de stock y alertas en tiempo real.
''')
class ContentManager:
def set_content(self, page_name):
main_content.clear()
with main_content:
ui.label(f'Página de {page_name}').classes('text-3xl mb-4')
if page_name == 'Dashboard':
ui.label('Aquí aparecerán las estadísticas resumidas.')
elif page_name == 'Inventario':
render_inventory_page()
elif page_name == 'CRM':
render_crm_page()
elif page_name == 'Producción':
render_production_page()
elif page_name == 'Logística':
render_logistics_page()
elif page_name == 'Productos':
render_products_page()
else:
ui.label(f'Contenido para {page_name} próximamente...').classes('text-xl')
content = ContentManager()
def render_crm_page():
ui.label('Gestión de Clientes (CRM)').classes('text-xl mb-4')
with ui.row().classes('w-full items-start gap-4'):
# Client List
with ui.card().classes('flex-1'):
ui.label('Clientes').classes('font-bold mb-2')
columns = [
{'name': 'name', 'label': 'Nombre', 'field': 'name', 'align': 'left'},
{'name': 'phone', 'label': 'Teléfono', 'field': 'phone'},
]
rows = [
{'name': 'Maria Garcia', 'phone': '600-000-000'},
{'name': 'Elena Rodriguez', 'phone': '611-111-111'},
]
ui.table(columns=columns, rows=rows, row_key='name').classes('w-full')
# New Client Form
with ui.card().classes('w-64'):
ui.label('Nuevo Cliente').classes('font-bold')
ui.input('Nombre Completo')
ui.input('Teléfono')
ui.textarea('Medidas').classes('w-full')
ui.button('Guardar Cliente', on_click=lambda: ui.notify('Cliente guardado'))
def render_production_page():
ui.label('Flujo de Producción (Kanban)').classes('text-xl mb-4')
with ui.row().classes('w-full mb-4'):
ui.button('Generar Hoja de Corte', icon='content_cut', on_click=lambda: ui.notify('Hoja de corte generada para todos los pedidos pendientes'))
with ui.row().classes('w-full justify-between gap-4'):
for status_label in ['Corte', 'Confección', 'Terminado']:
with ui.column().classes('bg-slate-50 p-4 rounded-lg flex-1 min-h-[400px] border border-slate-200'):
ui.label(status_label).classes('font-bold text-lg mb-4 text-slate-600 uppercase tracking-wider')
# Sample Card
with ui.card().classes('w-full mb-2 cursor-pointer hover:shadow-md transition-shadow'):
ui.label('Pedido #1001').classes('font-bold')
ui.label('Cliente: Maria Garcia')
ui.label('Estado: En Proceso').classes('text-xs text-blue-500')
with ui.row().classes('justify-end w-full'):
ui.button(icon='arrow_forward', variant='text').classes('p-0')
def render_products_page():
ui.label('Catálogo de Productos').classes('text-xl mb-4')
with ui.grid(columns=3).classes('w-full gap-4'):
# In a real app, this would fetch from the API
products = [
{'name': 'Tutú Profesional', 'sku': 'TUTU-001', 'total_cost': 65.50},
{'name': 'Maillot Clásico', 'sku': 'LEO-002', 'total_cost': 28.00},
{'name': 'Falda de Ballet', 'sku': 'SKT-003', 'total_cost': 15.20},
]
for p in products:
with ui.card():
ui.label(p['name']).classes('font-bold')
ui.label(f"SKU: {p['sku']}").classes('text-xs text-slate-500')
ui.label(f"Coste Total: {p['total_cost']}").classes('text-lg font-semibold text-primary')
ui.button('Editar Materiales', variant='text')
def render_inventory_page():
ui.label('Inventario de Materiales').classes('text-xl mb-2')
# Simple form to add material
with ui.card().classes('w-full mb-4'):
ui.label('Añadir Nuevo Material').classes('font-bold')
with ui.row():
name = ui.input('Nombre')
unit = ui.input('Unidad (ej. metros)')
stock = ui.number('Stock Inicial', value=0)
cost = ui.number('Coste por Unidad', value=0.0)
ui.button('Añadir Material', on_click=lambda: ui.notify(f'Añadido {name.value}'))
# Placeholder for table
ui.label('Niveles de Stock').classes('font-bold')
columns = [
{'name': 'name', 'label': 'Nombre', 'field': 'name', 'required': True, 'align': 'left'},
{'name': 'unit', 'label': 'Unidad', 'field': 'unit'},
{'name': 'stock', 'label': 'Stock', 'field': 'stock'},
{'name': 'cost', 'label': 'Coste', 'field': 'cost'},
]
rows = [
{'name': 'Satén Rosa', 'unit': 'metros', 'stock': 50, 'cost': 12.5},
{'name': 'Tul Blanco', 'unit': 'metros', 'stock': 120, 'cost': 5.0},
]
ui.table(columns=columns, rows=rows, row_key='name')
ui.run(title="Suite de Producción de Ballet", port=8080)