diff --git a/assets/images/placeholder_no_image.jpg b/assets/images/placeholder_no_image.jpg new file mode 100644 index 0000000..97b3e55 Binary files /dev/null and b/assets/images/placeholder_no_image.jpg differ diff --git a/assets/referencia/all_video_table.txt b/assets/referencia/all_video_table.txt new file mode 100644 index 0000000..602cf68 --- /dev/null +++ b/assets/referencia/all_video_table.txt @@ -0,0 +1,965 @@ +import 'package:cbluna_crm_lu/helpers/globals.dart'; +import 'package:cbluna_crm_lu/pages/content_manager/widget/edit_video_popup_neo.dart'; +import 'package:cbluna_crm_lu/pages/widgets/pluto_grid/pluto_grid_header.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:pluto_grid/pluto_grid.dart'; +import 'package:cbluna_crm_lu/pages/content_manager/widget/edit_video_popup.dart'; +import 'package:cbluna_crm_lu/pages/content_manager/widget/popup_detalle_video.dart'; +import 'package:cbluna_crm_lu/pages/content_manager/widget/popup_eliminar_video.dart'; +import 'package:cbluna_crm_lu/pages/widgets/animated_hover_button.dart'; + +import '../../../providers/videos_provider.dart'; +import '../../../theme/theme.dart'; + +import 'package:url_launcher/url_launcher.dart'; +import 'package:toggle_switch/toggle_switch.dart'; + +class AllVideoTable extends StatelessWidget { + const AllVideoTable({ + Key? key, + required this.providerAd, + }) : super(key: key); + + final VideosProvider providerAd; + + @override + Widget build(BuildContext context) { + return Flexible( + child: PlutoGrid( + key: UniqueKey(), + configuration: PlutoGridConfiguration( + enableMoveDownAfterSelecting: true, + enableMoveHorizontalInEditing: true, + localeText: const PlutoGridLocaleText.spanish(), + scrollbar: plutoGridScrollbarConfig(context), + style: plutoGridStyleConfigContentManager(context, rowHeight: 150), + columnFilter: const PlutoGridColumnFilterConfig( + filters: [ + ...FilterHelper.defaultFilters, + ], + ), + ), + columns: [ + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.numbers, + color: AppTheme.of(context).primaryBackground, + texto: 'ID', + ), + title: 'ID', + field: 'video_id', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 100, + type: PlutoColumnType.text(), + cellPadding: const EdgeInsets.all(0), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return rendererContext.cell.row.cells['categories']!.value + .toString() + .replaceAll(RegExp(r'^\[|\]$'), '') + .replaceAll('", "', ', ') + .replaceAll('null', '') + .isEmpty + ? Row( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + color: Colors.yellowAccent[400], + child: Center( + child: //icono warning + Icon( + Icons.warning, + color: AppTheme.of(context).tertiaryText, + size: 24, + ), + ), + ), + Expanded( + child: Container( + color: Colors.yellowAccent[400], + child: Center( + child: Text( + rendererContext.cell.value.toString(), + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ) + ], + ) + : Text( + rendererContext.cell.value.toString(), + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.play_arrow, + color: AppTheme.of(context).primaryBackground, + texto: 'Video', + ), + title: 'Video', + field: 'video_url', + width: 225, + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + try { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 250, + width: 100, + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20)), + child: Image.network( + rendererContext.row.cells['poster_path']!.value, + fit: BoxFit.cover, + )), + const SizedBox(width: 10), + Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppTheme.of(context).primaryBackground, + ), + child: AnimatedHoverButton( + icon: Icons.play_arrow, + tooltip: 'Iniciar', + size: 48, + primaryColor: AppTheme.of(context).primaryBackground, + secondaryColor: AppTheme.of(context).secondaryColor, + onTap: () async { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: PopupDetallesVideo( + tituloEncabezado: "Video actual", + url: rendererContext.cell.value.toString(), + titulo: + rendererContext.row.cells['title']!.value, + video: rendererContext.row.cells, + ), + // Widget personalizado + ); + }, + ); + }, + ), + ), + ], + ); + } catch (e) { + return Container( + color: Colors.transparent, + child: const Text("--", textAlign: TextAlign.center)); + } + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.calendar_month, + color: AppTheme.of(context).primaryBackground, + texto: 'Fecha de creación', + ), + title: 'Created at', + field: 'created_at', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 150, + type: PlutoColumnType.date(), + enableEditingMode: false, + enableAutoEditing: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + // Obtiene el valor de la fecha + String? fecha = rendererContext.row.cells['created_at']?.value; + + // Uso de operador ternario para verificar si hay fecha y formatearla + String fechaFormateada = (fecha != null && fecha.isNotEmpty) + ? DateFormat("dd 'de' MMMM 'de' yyyy", 'es_ES') + .format(DateTime.parse(fecha)) + : '--'; + + return SizedBox( + width: 250, + child: Text( + fechaFormateada, + textAlign: TextAlign.center, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.check_circle, + color: AppTheme.of(context).primaryBackground, + texto: 'Estado', + ), + title: 'Estado', + field: 'video_status', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 200, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + //usa el tooltip para mostrar el estado del video + return MouseRegion( + cursor: SystemMouseCursors.click, + child: ToggleSwitch( + centerText: true, + minWidth: 90.0, + cornerRadius: 20.0, + fontSize: 14, + activeBgColor: [AppTheme.of(context).primaryColor], + activeFgColor: Colors.white, + inactiveBgColor: + AppTheme.of(context).primaryText.withOpacity(0.7), + inactiveFgColor: Colors.white, + activeBgColors: [ + [AppTheme.of(context).primaryColor], + const [Colors.red] + ], + borderWidth: 2.0, + borderColor: [ + AppTheme.of(context).tertiaryText.withOpacity(0.7) + ], + labels: const ['Activo', 'Inactivo'], + initialLabelIndex: + rendererContext.cell.value == true ? 0 : 1, + onToggle: (index) { + rendererContext.cell.row.cells['video_status']!.value = + index == 0 + ? true + : false; //cambia el valor en la celda + providerAd.cambiarEstadoVideo( + rendererContext.cell.row.cells['video_id']!.value, + index == 0 ? true : false); + }, + ), + ); + }), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.star, + color: AppTheme.of(context).primaryBackground, + texto: 'Prioridad', + ), + title: 'Priority', + field: 'priority', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 125, + type: PlutoColumnType.number(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + width: 100, + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: rendererContext.row.cells['priority']!.value == 4 + ? const Color(0xFFD90D56).withOpacity(0.5) + : rendererContext.row.cells['priority']!.value == 3 + ? const Color(0xFFFFC700).withOpacity(0.5) + : rendererContext.row.cells['priority']!.value == + 2 + ? const Color(0xFF517EF2).withOpacity(0.5) + : const Color(0x5A0E2152)), + child: Text( + rendererContext.row.cells['priority']!.value == 4 + ? 'alta' + : rendererContext.row.cells['priority']!.value == 3 + ? 'media' + : rendererContext.row.cells['priority']!.value == 2 + ? 'baja' + : 'neutra', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + )); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.title, + color: AppTheme.of(context).primaryBackground, + texto: 'Titulo', + ), + title: 'Title', + field: 'title', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 325, + type: PlutoColumnType.text(), + enableEditingMode: false, + enableAutoEditing: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return SizedBox( + width: 150, + child: Text( + rendererContext.row.cells['title']!.value ?? '--', + textAlign: TextAlign.center, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.description, + color: AppTheme.of(context).primaryBackground, + texto: 'Descripción', + ), + title: 'Descripción', + field: 'overview', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 350, + type: PlutoColumnType.text(), + enableEditingMode: false, + enableAutoEditing: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return providerAd.showWarningsOnTable == true + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Container( + color: rendererContext.cell.value.isEmpty + ? Colors.yellowAccent[400] + : Colors.transparent, + child: Center( + child: Text( + rendererContext.cell.value.isEmpty + ? 'NO DESCRIPTION' + : rendererContext.cell.value, + textAlign: TextAlign.center, + style: TextStyle( + color: rendererContext.cell.value.isEmpty + ? AppTheme.of(context).tertiaryText + : AppTheme.of(context).primaryText, + fontSize: rendererContext.cell.value.isEmpty + ? 18 + : 14, + fontWeight: rendererContext.cell.value.isEmpty + ? FontWeight.w800 + : FontWeight.w500, + ), + ), + ), + ), + ), + ], + ) + : SizedBox( + width: 200, + child: Text( + rendererContext.row.cells['overview']!.value, + textAlign: TextAlign.center, + maxLines: 4, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.qr_code_2_rounded, + color: AppTheme.of(context).primaryBackground, + texto: 'QRs generados', + ), + title: 'QRs generados', + field: 'qr_codes', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 150, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + final qrCodes = rendererContext.row.cells['qr_codes']?.value; + + // Verifica que qrCodes no esté vacío antes de mostrar el botón + if (qrCodes != null && qrCodes.isNotEmpty) { + return Center( + child: ElevatedButton.icon( + icon: Icon(Icons.list_alt_rounded, size: 18), + label: Text( + 'Ver QRs', + style: TextStyle(fontSize: 13), + ), + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + backgroundColor: AppTheme.of(context).primaryColor, + foregroundColor: Colors.white, + ), + onPressed: () { + showDialog( + context: context, + builder: (_) => AlertDialog( + backgroundColor: + AppTheme.of(context).primaryBackground, + title: const Text('Lista de QRs'), + content: SizedBox( + width: 400, + height: 300, + child: ListView.builder( + itemCount: qrCodes + .split('\n') + .length, // Divide los QR Codes concatenados + itemBuilder: (context, index) { + try { + final qrData = qrCodes.split( + '\n')[index]; // Obtiene el QR individual + return Padding( + padding: + const EdgeInsets.symmetric(vertical: 4), + child: Text( + qrData, + style: const TextStyle(fontSize: 14), + ), + ); + } catch (e) { + return Text( + 'Error al leer QR: $e', + style: const TextStyle(color: Colors.red), + ); + } + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Cerrar'), + ), + ], + ), + ); + }, + ), + ); + } else { + return Center( + child: Text( + 'GLOBAL', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.w900, + ), + )); + } + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.link, + color: AppTheme.of(context).primaryBackground, + texto: 'Enlace', + ), + title: 'OutLink', + field: 'url_ad', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 100, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + String? url = rendererContext.row.cells['url_ad']?.value; + bool isValidUrl = url != null && url != 'null' && url.isNotEmpty; + return providerAd.showWarningsOnTable == true + ? Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + color: isValidUrl + ? Colors.transparent + : Colors.yellowAccent[400], + child: IconButton( + onPressed: () async { + if (isValidUrl) { + String finalUrl = + url!.startsWith(RegExp(r'http://|https://')) + ? url + : 'http://$url'; + if (await canLaunchUrl(Uri.parse(finalUrl))) { + await launchUrl(Uri.parse(finalUrl)); + } else { + print('No se puede abrir el enlace'); + } + } else { + print('No se puede abrir el enlace'); + } + }, + icon: Icon( + isValidUrl ? Icons.link : Icons.link_off, + color: isValidUrl + ? AppTheme.of(context).primaryColor + : AppTheme.of(context).tertiaryText, + semanticLabel: + isValidUrl ? 'Open link' : 'No link', + ), + ), + ), + if (!isValidUrl) + const Text('No link added', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w800)), + ], + ) + : IconButton( + onPressed: () async { + if (isValidUrl) { + // Asegurarse de que 'url' comience con 'http://' o 'https://' + String finalUrl = + url!.startsWith(RegExp(r'http://|https://')) + ? url + : 'http://$url'; + if (await canLaunchUrl(Uri.parse(finalUrl))) { + await launchUrl(Uri.parse(finalUrl)); + } else { + print('No se puede abrir el enlace'); + } + } else { + print('No se puede abrir el enlace'); + } + }, + icon: Icon( + // Usar 'isValidUrl' para determinar el ícono a mostrar. + isValidUrl ? Icons.link : Icons.link_off, + color: AppTheme.of(context).primaryColor, + ), + ); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.category, + color: AppTheme.of(context).primaryBackground, + texto: 'Categoría de video', + ), + title: 'Categoría', + field: 'categories', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 200, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + var textoLimpio = rendererContext.cell.value + .toString() + .replaceAll(RegExp(r'^\[|\]$'), '') + .replaceAll('", "', ', ') + .replaceAll('null', ''); + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Container( + color: textoLimpio.isEmpty + ? Colors.yellowAccent[400] + : Colors.transparent, + child: Center( + child: Text( + textoLimpio.isEmpty ? 'NO CATEGORIES' : textoLimpio, + textAlign: TextAlign.center, + style: TextStyle( + color: textoLimpio.isEmpty + ? AppTheme.of(context).tertiaryText + : AppTheme.of(context).primaryText, + fontSize: textoLimpio.isEmpty ? 18 : 16, + fontWeight: textoLimpio.isEmpty + ? FontWeight.w800 + : FontWeight.w500, + ), + ), + ), + ), + ), + ], + ); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.person, + color: AppTheme.of(context).primaryBackground, + texto: 'Patrocinador', + ), + title: 'Partner', + field: 'partner', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 150, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return providerAd.showWarningsOnTable == true + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: Container( + color: rendererContext.cell.value.isEmpty + ? Colors.yellowAccent[400] + : Colors.transparent, + child: Center( + child: Text( + rendererContext.cell.value.isEmpty + ? 'NO PARTNER' + : rendererContext.cell.value, + textAlign: TextAlign.center, + style: TextStyle( + color: rendererContext.cell.value.isEmpty + ? AppTheme.of(context).tertiaryText + : AppTheme.of(context).primaryText, + fontSize: rendererContext.cell.value.isEmpty + ? 18 + : 16, + fontWeight: rendererContext.cell.value.isEmpty + ? FontWeight.w800 + : FontWeight.w500, + ), + ), + ), + ), + ), + ], + ) + : SizedBox( + width: 200, + child: Text( + rendererContext.row.cells['partner']!.value ?? '--', + textAlign: TextAlign.center, + maxLines: 4, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 18, + fontWeight: FontWeight.w500, + ), + ), + ); + }, + ), + /* PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.calendar_today, + color: AppTheme.of(context).primaryBackground, + texto: 'Fecha expiración', + ), + title: 'Exp. date', + field: 'expirationDate', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 180, + type: PlutoColumnType.date(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + ), */ + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.timelapse, + color: AppTheme.of(context).primaryBackground, + texto: 'Duración', + ), + title: 'Duration', + field: 'duration_video', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 130, + type: PlutoColumnType.number(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + int durationInSeconds = rendererContext.cell.value; + String minutes = + (durationInSeconds ~/ 60).toString().padLeft(2, '0'); + String seconds = + (durationInSeconds % 60).toString().padLeft(2, '0'); + return Text( + '$minutes:$seconds', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.edit, + color: AppTheme.of(context).primaryBackground, + texto: 'Editar', + ), + title: 'Edit', + field: 'editar', + width: 100, + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + type: PlutoColumnType.number(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + alignment: Alignment.center, + child: AnimatedHoverButton( + icon: Icons.edit, + tooltip: 'Edit', + primaryColor: AppTheme.of(context).primaryBackground, + secondaryColor: AppTheme.of(context).primaryColor, + onTap: () async { + providerAd.resetAllvideoData(); + final List qrsAsociados = + await providerAd.getQrsByVideoId(rendererContext + .cell.row.cells['video_id']!.value); + + providerAd.listaQrsSeleccionados = qrsAsociados; + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + providerAd.selectedVideoId = rendererContext + .cell.row.cells['video_id']!.value; + + providerAd.tituloVideo = rendererContext + .cell.row.cells['title']!.value; + + providerAd.videoPoints = rendererContext + .cell.row.cells['points']!.value; + + providerAd.videoName = rendererContext + .cell.row.cells['title']!.value; + + providerAd.videoPatner = rendererContext + .cell.row.cells['partner']!.value; + + providerAd.descripcionVideo = rendererContext + .cell.row.cells['overview']!.value; + + providerAd.videoOutlink = rendererContext + .cell.row.cells['url_ad']!.value; + //video_poster_file + providerAd.videoCoverFile = rendererContext + .cell.row.cells['poster_file_name']!.value; + + providerAd.videoCoverFileNameNoModif = + rendererContext.cell.row + .cells['poster_file_name']!.value; + + providerAd.selectePriority = rendererContext + .row.cells['priority']!.value == + 4 + ? 'alta' + : rendererContext + .row.cells['priority']!.value == + 3 + ? 'media' + : rendererContext.row.cells['priority']! + .value == + 2 + ? 'baja' + : 'neutra'; + + providerAd.videoPath = rendererContext + .cell.row.cells['video_url']!.value; + + providerAd.videoPosterPath = rendererContext + .cell.row.cells['poster_path']!.value; + + return AlertDialog( + backgroundColor: + AppTheme.of(context).primaryBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(50), + ), + content: PopupEditVideoNeo( + provider: providerAd, + editMode: true, + currentCategories: rendererContext + .cell.row.cells['categories']!.value, + )); + }, + ); + }, + ), + ), + ], + ); + }), + PlutoColumn( + titleSpan: plutoGridHeader( + context: context, + icono: Icons.delete, + color: AppTheme.of(context).primaryBackground, + texto: 'Eliminar', + ), + title: 'Delete', + field: 'eliminar', + width: 100, + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + type: PlutoColumnType.number(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + alignment: Alignment.center, + child: AnimatedHoverButton( + icon: Icons.delete, + tooltip: 'Delete this video', + primaryColor: AppTheme.of(context).primaryBackground, + secondaryColor: Colors.redAccent, + onTap: () async { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return EliminarVideoPopup( + idVideo: rendererContext + .cell.row.cells['video_id']!.value, + nombreVideo: rendererContext + .cell.row.cells['title']!.value, + videoFileName: rendererContext + .cell.row.cells['video_file_name']!.value, + posterFileName: rendererContext + .cell.row.cells['poster_file_name']!.value, + videoUrl: rendererContext + .cell.row.cells['video_url']!.value, + ); + }, + ); + }, + ), + ), + ], + ); + }), + ], + rows: providerAd.allVideoRows, + onLoaded: (event) async { + providerAd.listStateManager.add(event.stateManager); + }, + createFooter: (stateManager) { + stateManager.setPageSize(10, notify: false); // default 40 + + return PlutoPagination(stateManager); + }, + ), + ); + } +} diff --git a/assets/referencia/landingMap1.png b/assets/referencia/landingMap1.png new file mode 100644 index 0000000..cad17f0 Binary files /dev/null and b/assets/referencia/landingMap1.png differ diff --git a/assets/referencia/landingMap2.png b/assets/referencia/landingMap2.png new file mode 100644 index 0000000..19e5921 Binary files /dev/null and b/assets/referencia/landingMap2.png differ diff --git a/lib/main.dart b/lib/main.dart index 54114e7..1625bf9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -52,37 +52,6 @@ void main() async { ); } -/* class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); - - @override - State createState() => _MyAppState(); - - static _MyAppState of(BuildContext context) => - context.findAncestorStateOfType<_MyAppState>()!; -} - -class _MyAppState extends State { - Locale _locale = const Locale('es'); - ThemeMode _themeMode = ThemeMode.light; - - void setLocale(Locale value) => setState(() => _locale = value); - void setThemeMode(ThemeMode mode) => setState(() => _themeMode = mode); - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Nethive Neo', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - darkTheme: ThemeData.dark(), - themeMode: _themeMode, - locale: _locale, - home: const Placeholder(), // Cambia esto por tu pantalla inicial - ); - } -} */ class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); diff --git a/lib/pages/empresa_negocios/empresa_negocios_page.dart b/lib/pages/empresa_negocios/empresa_negocios_page.dart new file mode 100644 index 0000000..0bb7631 --- /dev/null +++ b/lib/pages/empresa_negocios/empresa_negocios_page.dart @@ -0,0 +1,287 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; +import 'package:nethive_neo/pages/empresa_negocios/widgets/empresa_selector_sidebar.dart'; +import 'package:nethive_neo/pages/empresa_negocios/widgets/negocios_table.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class EmpresaNegociosPage extends StatefulWidget { + const EmpresaNegociosPage({Key? key}) : super(key: key); + + @override + State createState() => _EmpresaNegociosPageState(); +} + +class _EmpresaNegociosPageState extends State { + bool showMapView = false; + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppTheme.of(context).primaryBackground, + body: Consumer( + builder: (context, provider, child) { + return Row( + children: [ + // Sidebar izquierdo con empresas + SizedBox( + width: 300, + child: EmpresaSelectorSidebar( + provider: provider, + onEmpresaSelected: (empresaId) { + provider.setEmpresaSeleccionada(empresaId); + }, + ), + ), + + // Área principal + Expanded( + child: Container( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header con título y switch + _buildHeader(provider), + + const SizedBox(height: 16), + + // Contenido principal (tabla o mapa) + Expanded( + child: showMapView + ? _buildMapView() + : _buildTableView(provider), + ), + ], + ), + ), + ), + ], + ); + }, + ), + ); + } + + Widget _buildHeader(EmpresasNegociosProvider provider) { + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + provider.empresaSeleccionada != null + ? 'Sucursales de ${provider.empresaSeleccionada!.nombre}' + : 'Selecciona una empresa para ver sus sucursales', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + if (provider.empresaSeleccionada != null) + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 6, + ), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '${provider.negocios.length} sucursales', + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(width: 12), + Text( + provider.empresaSeleccionada!.nombre, + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 16, + ), + ), + ], + ), + ], + ), + ), + + // Switch para cambiar vista + Column( + children: [ + Text( + 'Vista de Mapa', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 8), + Switch( + value: showMapView, + onChanged: (value) { + setState(() { + showMapView = value; + }); + }, + activeColor: AppTheme.of(context).primaryColor, + ), + ], + ), + ], + ), + ); + } + + Widget _buildTableView(EmpresasNegociosProvider provider) { + if (provider.empresaSeleccionada == null) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.business, + size: 80, + color: AppTheme.of(context).secondaryText, + ), + const SizedBox(height: 16), + Text( + 'Selecciona una empresa para ver sus sucursales', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 18, + ), + ), + ], + ), + ); + } + + return Container( + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + // Header de la tabla + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + ), + ), + child: Row( + children: [ + Icon( + Icons.store, + color: Colors.white, + size: 24, + ), + const SizedBox(width: 12), + Text( + 'Sucursales', + style: const TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const Spacer(), + Text( + 'Mostrando 1 a ${provider.negocios.length} de ${provider.negocios.length} sucursales', + style: const TextStyle( + color: Colors.white70, + fontSize: 14, + ), + ), + ], + ), + ), + + // Tabla de negocios + Expanded( + child: NegociosTable(provider: provider), + ), + ], + ), + ); + } + + Widget _buildMapView() { + return Container( + decoration: BoxDecoration( + color: Colors.blue.withOpacity(0.3), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.blue, + width: 2, + ), + ), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.map, + size: 80, + color: Colors.blue[700], + ), + const SizedBox(height: 16), + Text( + 'Vista de Mapa', + style: TextStyle( + color: Colors.blue[700], + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + 'Próximamente se implementará el mapa con las ubicaciones de las sucursales', + style: TextStyle( + color: Colors.blue[600], + fontSize: 16, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/empresa_negocios/widgets/add_empresa_dialog.dart b/lib/pages/empresa_negocios/widgets/add_empresa_dialog.dart new file mode 100644 index 0000000..7413404 --- /dev/null +++ b/lib/pages/empresa_negocios/widgets/add_empresa_dialog.dart @@ -0,0 +1,357 @@ +import 'package:flutter/material.dart'; +import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class AddEmpresaDialog extends StatefulWidget { + final EmpresasNegociosProvider provider; + + const AddEmpresaDialog({ + Key? key, + required this.provider, + }) : super(key: key); + + @override + State createState() => _AddEmpresaDialogState(); +} + +class _AddEmpresaDialogState extends State { + final _formKey = GlobalKey(); + final _nombreController = TextEditingController(); + final _rfcController = TextEditingController(); + final _direccionController = TextEditingController(); + final _telefonoController = TextEditingController(); + final _emailController = TextEditingController(); + + bool _isLoading = false; + + @override + void dispose() { + _nombreController.dispose(); + _rfcController.dispose(); + _direccionController.dispose(); + _telefonoController.dispose(); + _emailController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: AppTheme.of(context).primaryBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + title: Row( + children: [ + Icon( + Icons.business, + color: AppTheme.of(context).primaryColor, + ), + const SizedBox(width: 8), + Text( + 'Añadir Nueva Empresa', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + content: SizedBox( + width: 500, + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Nombre + TextFormField( + controller: _nombreController, + decoration: InputDecoration( + labelText: 'Nombre de la empresa *', + hintText: 'Ej: TechCorp Solutions', + prefixIcon: Icon(Icons.business), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'El nombre es requerido'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // RFC + TextFormField( + controller: _rfcController, + decoration: InputDecoration( + labelText: 'RFC *', + hintText: 'Ej: ABC123456789', + prefixIcon: Icon(Icons.assignment), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'El RFC es requerido'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Dirección + TextFormField( + controller: _direccionController, + maxLines: 3, + decoration: InputDecoration( + labelText: 'Dirección *', + hintText: 'Dirección completa de la empresa', + prefixIcon: Icon(Icons.location_on), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'La dirección es requerida'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Teléfono + TextFormField( + controller: _telefonoController, + decoration: InputDecoration( + labelText: 'Teléfono *', + hintText: 'Ej: +52 555 123 4567', + prefixIcon: Icon(Icons.phone), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'El teléfono es requerido'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Email + TextFormField( + controller: _emailController, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'Email *', + hintText: 'contacto@empresa.com', + prefixIcon: Icon(Icons.email), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'El email es requerido'; + } + if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$') + .hasMatch(value)) { + return 'Email inválido'; + } + return null; + }, + ), + const SizedBox(height: 24), + + // Sección de archivos + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.grey.withOpacity(0.3), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Archivos (Opcional)', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.of(context).primaryText, + ), + ), + const SizedBox(height: 12), + + // Logo + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: widget.provider.selectLogo, + icon: Icon(Icons.image), + label: Text( + widget.provider.logoFileName ?? + 'Seleccionar Logo', + overflow: TextOverflow.ellipsis, + ), + ), + ), + if (widget.provider.logoToUpload != null) ...[ + const SizedBox(width: 8), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), + child: widget.provider.getImageWidget( + widget.provider.logoToUpload, + height: 40, + width: 40, + ), + ), + ], + ], + ), + const SizedBox(height: 8), + + // Imagen + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: widget.provider.selectImagen, + icon: Icon(Icons.photo), + label: Text( + widget.provider.imagenFileName ?? + 'Seleccionar Imagen', + overflow: TextOverflow.ellipsis, + ), + ), + ), + if (widget.provider.imagenToUpload != null) ...[ + const SizedBox(width: 8), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), + child: widget.provider.getImageWidget( + widget.provider.imagenToUpload, + height: 40, + width: 40, + ), + ), + ], + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + actions: [ + TextButton( + onPressed: _isLoading + ? null + : () { + widget.provider.resetFormData(); + Navigator.of(context).pop(); + }, + child: Text( + 'Cancelar', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + ), + ), + ), + ElevatedButton( + onPressed: _isLoading ? null : _crearEmpresa, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.of(context).primaryColor, + foregroundColor: Colors.white, + ), + child: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Text('Crear Empresa'), + ), + ], + ); + } + + Future _crearEmpresa() async { + if (!_formKey.currentState!.validate()) return; + + setState(() { + _isLoading = true; + }); + + try { + final success = await widget.provider.crearEmpresa( + nombre: _nombreController.text.trim(), + rfc: _rfcController.text.trim(), + direccion: _direccionController.text.trim(), + telefono: _telefonoController.text.trim(), + email: _emailController.text.trim(), + ); + + if (mounted) { + if (success) { + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Empresa creada exitosamente'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Error al crear la empresa'), + backgroundColor: Colors.red, + ), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } +} diff --git a/lib/pages/empresa_negocios/widgets/add_negocio_dialog.dart b/lib/pages/empresa_negocios/widgets/add_negocio_dialog.dart new file mode 100644 index 0000000..b680c9f --- /dev/null +++ b/lib/pages/empresa_negocios/widgets/add_negocio_dialog.dart @@ -0,0 +1,487 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class AddNegocioDialog extends StatefulWidget { + final EmpresasNegociosProvider provider; + final String empresaId; + + const AddNegocioDialog({ + Key? key, + required this.provider, + required this.empresaId, + }) : super(key: key); + + @override + State createState() => _AddNegocioDialogState(); +} + +class _AddNegocioDialogState extends State { + final _formKey = GlobalKey(); + final _nombreController = TextEditingController(); + final _direccionController = TextEditingController(); + final _latitudController = TextEditingController(); + final _longitudController = TextEditingController(); + final _tipoLocalController = TextEditingController(text: 'Sucursal'); + + bool _isLoading = false; + + final List _tiposLocal = [ + 'Sucursal', + 'Oficina Central', + 'Bodega', + 'Centro de Distribución', + 'Punto de Venta', + 'Otro' + ]; + + @override + void dispose() { + _nombreController.dispose(); + _direccionController.dispose(); + _latitudController.dispose(); + _longitudController.dispose(); + _tipoLocalController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + backgroundColor: AppTheme.of(context).primaryBackground, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + title: Row( + children: [ + Icon( + Icons.store, + color: AppTheme.of(context).primaryColor, + ), + const SizedBox(width: 8), + Text( + 'Añadir Nueva Sucursal', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + content: SizedBox( + width: 500, + child: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Información de la empresa + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.3), + ), + ), + child: Row( + children: [ + Icon( + Icons.business, + color: AppTheme.of(context).primaryColor, + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Empresa: ${widget.provider.empresaSeleccionada?.nombre ?? ""}', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + const SizedBox(height: 20), + + // Nombre + TextFormField( + controller: _nombreController, + decoration: InputDecoration( + labelText: 'Nombre de la sucursal *', + hintText: 'Ej: Sucursal Centro, Sede Norte', + prefixIcon: Icon(Icons.store), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'El nombre es requerido'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Dirección + TextFormField( + controller: _direccionController, + maxLines: 3, + decoration: InputDecoration( + labelText: 'Dirección *', + hintText: 'Dirección completa de la sucursal', + prefixIcon: Icon(Icons.location_on), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'La dirección es requerida'; + } + return null; + }, + ), + const SizedBox(height: 16), + + // Tipo de local + DropdownButtonFormField( + value: _tipoLocalController.text, + decoration: InputDecoration( + labelText: 'Tipo de local *', + prefixIcon: Icon(Icons.category), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + items: _tiposLocal.map((String tipo) { + return DropdownMenuItem( + value: tipo, + child: Text(tipo), + ); + }).toList(), + onChanged: (String? newValue) { + if (newValue != null) { + _tipoLocalController.text = newValue; + } + }, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Selecciona un tipo de local'; + } + return null; + }, + ), + const SizedBox(height: 20), + + // Coordenadas + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.grey.withOpacity(0.3), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.map, + color: AppTheme.of(context).primaryColor, + size: 20, + ), + const SizedBox(width: 8), + Text( + 'Coordenadas Geográficas *', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.of(context).primaryText, + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + // Latitud + Expanded( + child: TextFormField( + controller: _latitudController, + keyboardType: + const TextInputType.numberWithOptions( + decimal: true, + signed: true, + ), + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^-?\d*\.?\d*'), + ), + ], + decoration: InputDecoration( + labelText: 'Latitud', + hintText: 'Ej: 19.4326', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Requerido'; + } + final lat = double.tryParse(value); + if (lat == null) { + return 'Número inválido'; + } + if (lat < -90 || lat > 90) { + return 'Rango: -90 a 90'; + } + return null; + }, + ), + ), + const SizedBox(width: 16), + // Longitud + Expanded( + child: TextFormField( + controller: _longitudController, + keyboardType: + const TextInputType.numberWithOptions( + decimal: true, + signed: true, + ), + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'^-?\d*\.?\d*'), + ), + ], + decoration: InputDecoration( + labelText: 'Longitud', + hintText: 'Ej: -99.1332', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + validator: (value) { + if (value == null || value.trim().isEmpty) { + return 'Requerido'; + } + final lng = double.tryParse(value); + if (lng == null) { + return 'Número inválido'; + } + if (lng < -180 || lng > 180) { + return 'Rango: -180 a 180'; + } + return null; + }, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + 'Tip: Puedes obtener las coordenadas desde Google Maps', + style: TextStyle( + fontSize: 12, + color: AppTheme.of(context).secondaryText, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ), + const SizedBox(height: 20), + + // Sección de archivos + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).secondaryBackground, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.grey.withOpacity(0.3), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Archivos (Opcional)', + style: TextStyle( + fontWeight: FontWeight.bold, + color: AppTheme.of(context).primaryText, + ), + ), + const SizedBox(height: 12), + + // Logo + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: widget.provider.selectLogo, + icon: Icon(Icons.image), + label: Text( + widget.provider.logoFileName ?? + 'Seleccionar Logo', + overflow: TextOverflow.ellipsis, + ), + ), + ), + if (widget.provider.logoToUpload != null) ...[ + const SizedBox(width: 8), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), + child: widget.provider.getImageWidget( + widget.provider.logoToUpload, + height: 40, + width: 40, + ), + ), + ], + ], + ), + const SizedBox(height: 8), + + // Imagen + Row( + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: widget.provider.selectImagen, + icon: Icon(Icons.photo), + label: Text( + widget.provider.imagenFileName ?? + 'Seleccionar Imagen', + overflow: TextOverflow.ellipsis, + ), + ), + ), + if (widget.provider.imagenToUpload != null) ...[ + const SizedBox(width: 8), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(4), + ), + child: widget.provider.getImageWidget( + widget.provider.imagenToUpload, + height: 40, + width: 40, + ), + ), + ], + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + actions: [ + TextButton( + onPressed: _isLoading + ? null + : () { + widget.provider.resetFormData(); + Navigator.of(context).pop(); + }, + child: Text( + 'Cancelar', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + ), + ), + ), + ElevatedButton( + onPressed: _isLoading ? null : _crearNegocio, + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.of(context).primaryColor, + foregroundColor: Colors.white, + ), + child: _isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ) + : const Text('Crear Sucursal'), + ), + ], + ); + } + + Future _crearNegocio() async { + if (!_formKey.currentState!.validate()) return; + + setState(() { + _isLoading = true; + }); + + try { + final success = await widget.provider.crearNegocio( + empresaId: widget.empresaId, + nombre: _nombreController.text.trim(), + direccion: _direccionController.text.trim(), + latitud: double.parse(_latitudController.text.trim()), + longitud: double.parse(_longitudController.text.trim()), + tipoLocal: _tipoLocalController.text.trim(), + ); + + if (mounted) { + if (success) { + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Sucursal creada exitosamente'), + backgroundColor: Colors.green, + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Error al crear la sucursal'), + backgroundColor: Colors.red, + ), + ); + } + } + } catch (e) { + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Error: $e'), + backgroundColor: Colors.red, + ), + ); + } + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } +} diff --git a/lib/pages/empresa_negocios/widgets/empresa_selector_sidebar.dart b/lib/pages/empresa_negocios/widgets/empresa_selector_sidebar.dart new file mode 100644 index 0000000..738175d --- /dev/null +++ b/lib/pages/empresa_negocios/widgets/empresa_selector_sidebar.dart @@ -0,0 +1,285 @@ +import 'package:flutter/material.dart'; +import 'package:nethive_neo/helpers/globals.dart'; +import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; +import 'package:nethive_neo/pages/empresa_negocios/widgets/add_empresa_dialog.dart'; +import 'package:nethive_neo/pages/empresa_negocios/widgets/add_negocio_dialog.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class EmpresaSelectorSidebar extends StatelessWidget { + final EmpresasNegociosProvider provider; + final Function(String) onEmpresaSelected; + + const EmpresaSelectorSidebar({ + Key? key, + required this.provider, + required this.onEmpresaSelected, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + color: AppTheme.of(context).secondaryBackground, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Seleccionar Empresa', + style: TextStyle( + color: Colors.white, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Text( + 'Haz una empresas para ver sus sucursales', + style: TextStyle( + color: Colors.white70, + fontSize: 14, + ), + ), + ], + ), + ), + + // Lista de empresas + Expanded( + child: ListView.builder( + padding: const EdgeInsets.all(8), + itemCount: provider.empresas.length, + itemBuilder: (context, index) { + final empresa = provider.empresas[index]; + final isSelected = provider.empresaSeleccionadaId == empresa.id; + + return Container( + margin: const EdgeInsets.only(bottom: 8), + decoration: BoxDecoration( + color: isSelected + ? AppTheme.of(context).primaryColor.withOpacity(0.1) + : AppTheme.of(context).primaryBackground, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected + ? AppTheme.of(context).primaryColor + : Colors.grey.withOpacity(0.3), + width: isSelected ? 2 : 1, + ), + ), + child: InkWell( + onTap: () => onEmpresaSelected(empresa.id), + borderRadius: BorderRadius.circular(12), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + // Logo de la empresa + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor, + borderRadius: BorderRadius.circular(8), + ), + child: empresa.logoUrl != null + ? ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Image.network( + "${supabaseLU.supabaseUrl}/storage/v1/object/public/nethive/logos/${empresa.logoUrl}", + fit: BoxFit.cover, + errorBuilder: + (context, error, stackTrace) { + return Image.asset( + 'assets/images/placeholder_no_image.jpg', + fit: BoxFit.cover, + ); + }, + ), + ) + : Image.asset( + 'assets/images/placeholder_no_image.jpg', + fit: BoxFit.cover, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + empresa.nombre, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + 'Tecnología', + style: TextStyle( + color: + AppTheme.of(context).secondaryText, + fontSize: 12, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + 'Sucursales: ${isSelected ? provider.negocios.length : '...'}', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 12, + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + + // Botón añadir empresa + Padding( + padding: const EdgeInsets.all(16), + child: SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () { + showDialog( + context: context, + builder: (context) => AddEmpresaDialog(provider: provider), + ); + }, + icon: const Icon(Icons.add, color: Colors.white), + label: const Text( + 'Añadir Empresa', + style: TextStyle( + color: Colors.white, + fontWeight: FontWeight.w600, + ), + ), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.of(context).primaryColor, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + ), + ), + ), + + // Información de empresa seleccionada + if (provider.empresaSeleccionada != null) ...[ + Container( + margin: const EdgeInsets.fromLTRB(16, 0, 16, 16), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: AppTheme.of(context).primaryColor.withOpacity(0.3), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Empresa seleccionada:', + style: TextStyle( + color: AppTheme.of(context).secondaryText, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(height: 4), + Text( + provider.empresaSeleccionada!.nombre, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + Icons.store, + size: 16, + color: AppTheme.of(context).primaryColor, + ), + const SizedBox(width: 4), + Text( + '${provider.negocios.length} Sucursales', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + const SizedBox(height: 12), + SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: () { + showDialog( + context: context, + builder: (context) => AddNegocioDialog( + provider: provider, + empresaId: provider.empresaSeleccionada!.id, + ), + ); + }, + icon: const Icon(Icons.add, size: 16), + label: const Text( + 'Añadir Sucursal', + style: TextStyle(fontSize: 12), + ), + style: ElevatedButton.styleFrom( + backgroundColor: AppTheme.of(context).primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + ), + ), + ], + ), + ), + ], + ], + ), + ); + } +} diff --git a/lib/pages/empresa_negocios/widgets/negocios_table.dart b/lib/pages/empresa_negocios/widgets/negocios_table.dart new file mode 100644 index 0000000..0b33b8d --- /dev/null +++ b/lib/pages/empresa_negocios/widgets/negocios_table.dart @@ -0,0 +1,447 @@ +import 'package:flutter/material.dart'; +import 'package:pluto_grid/pluto_grid.dart'; +import 'package:nethive_neo/providers/nethive/empresas_negocios_provider.dart'; +import 'package:nethive_neo/pages/widgets/animated_hover_button.dart'; +import 'package:nethive_neo/theme/theme.dart'; + +class NegociosTable extends StatelessWidget { + final EmpresasNegociosProvider provider; + + const NegociosTable({ + Key? key, + required this.provider, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return PlutoGrid( + key: UniqueKey(), + configuration: PlutoGridConfiguration( + enableMoveDownAfterSelecting: true, + enableMoveHorizontalInEditing: true, + localeText: const PlutoGridLocaleText.spanish(), + scrollbar: PlutoGridScrollbarConfig( + draggableScrollbar: true, + isAlwaysShown: false, + onlyDraggingThumb: true, + enableScrollAfterDragEnd: true, + scrollbarThickness: 12, + scrollbarThicknessWhileDragging: 16, + hoverWidth: 20, + scrollBarColor: AppTheme.of(context).primaryColor.withOpacity(0.7), + scrollBarTrackColor: Colors.grey.withOpacity(0.2), + scrollbarRadius: const Radius.circular(8), + scrollbarRadiusWhileDragging: const Radius.circular(10), + ), + style: PlutoGridStyleConfig( + gridBorderColor: Colors.grey.withOpacity(0.3), + activatedBorderColor: AppTheme.of(context).primaryColor, + inactivatedBorderColor: Colors.grey.withOpacity(0.3), + gridBackgroundColor: AppTheme.of(context).primaryBackground, + rowColor: AppTheme.of(context).secondaryBackground, + activatedColor: AppTheme.of(context).primaryColor.withOpacity(0.1), + checkedColor: AppTheme.of(context).primaryColor.withOpacity(0.2), + cellTextStyle: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + ), + columnTextStyle: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.bold, + ), + menuBackgroundColor: AppTheme.of(context).secondaryBackground, + gridBorderRadius: BorderRadius.circular(8), + rowHeight: 60, + ), + columnFilter: const PlutoGridColumnFilterConfig( + filters: [ + ...FilterHelper.defaultFilters, + ], + ), + ), + columns: [ + PlutoColumn( + title: 'ID', + field: 'id', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 100, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Text( + rendererContext.cell.value.toString().substring(0, 8) + '...', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ); + }, + ), + PlutoColumn( + title: 'Nombre de Sucursal', + field: 'nombre', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 200, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Container( + padding: const EdgeInsets.all(8), + child: Row( + children: [ + // Logo del negocio + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor, + borderRadius: BorderRadius.circular(6), + ), + child: + rendererContext.row.cells['logo_url']?.value != null && + rendererContext.row.cells['logo_url']!.value + .toString() + .isNotEmpty + ? ClipRRect( + borderRadius: BorderRadius.circular(6), + child: Image.network( + rendererContext.row.cells['logo_url']!.value + .toString(), + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + 'assets/images/placeholder_no_image.jpg', + fit: BoxFit.cover, + ); + }, + ), + ) + : Image.asset( + 'assets/images/placeholder_no_image.jpg', + fit: BoxFit.cover, + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + rendererContext.cell.value.toString(), + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + }, + ), + PlutoColumn( + title: 'Ciudad', + field: 'direccion', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 180, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + // Extraer solo la ciudad de la dirección completa + String direccionCompleta = rendererContext.cell.value.toString(); + String ciudad = _extraerCiudad(direccionCompleta); + return Container( + padding: const EdgeInsets.all(8), + child: Text( + ciudad, + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + }, + ), + PlutoColumn( + title: 'Empleados', + field: 'tipo_local', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 120, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + // Simulamos cantidad de empleados basado en el tipo de local + String empleados = + rendererContext.cell.value.toString() == 'Sucursal' + ? '95' + : '120'; + return Container( + padding: const EdgeInsets.all(8), + child: Text( + '$empleados empleados', + textAlign: TextAlign.center, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ); + }, + ), + PlutoColumn( + title: 'Dirección Completa', + field: 'direccion_completa', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 280, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + // Usamos la dirección del row en lugar del cell + String direccion = + rendererContext.row.cells['direccion']?.value.toString() ?? ''; + return Container( + padding: const EdgeInsets.all(8), + child: Text( + direccion, + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 14, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ); + }, + ), + PlutoColumn( + title: 'Coordenadas', + field: 'latitud', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 150, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + final latitud = rendererContext.row.cells['latitud']?.value ?? '0'; + final longitud = + rendererContext.row.cells['longitud']?.value ?? '0'; + return Container( + padding: const EdgeInsets.all(8), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Lat: ${double.parse(latitud.toString()).toStringAsFixed(4)}', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 12, + ), + ), + Text( + 'Lng: ${double.parse(longitud.toString()).toStringAsFixed(4)}', + style: TextStyle( + color: AppTheme.of(context).primaryText, + fontSize: 12, + ), + ), + ], + ), + ); + }, + ), + PlutoColumn( + title: 'Acciones', + field: 'editar', + titleTextAlign: PlutoColumnTextAlign.center, + textAlign: PlutoColumnTextAlign.center, + width: 120, + type: PlutoColumnType.text(), + enableEditingMode: false, + backgroundColor: AppTheme.of(context).primaryColor, + enableContextMenu: false, + enableDropToResize: false, + renderer: (rendererContext) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Botón editar + Tooltip( + message: 'Editar negocio', + child: InkWell( + onTap: () { + // TODO: Implementar edición + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Función de edición próximamente')), + ); + }, + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: AppTheme.of(context).primaryColor, + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.edit, + color: Colors.white, + size: 16, + ), + ), + ), + ), + const SizedBox(width: 8), + // Botón ver componentes + Tooltip( + message: 'Ver componentes', + child: InkWell( + onTap: () { + // TODO: Navegar a componentes + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + 'Navegando a componentes de ${rendererContext.row.cells['nombre']?.value}', + ), + ), + ); + }, + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.orange, + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.inventory_2, + color: Colors.white, + size: 16, + ), + ), + ), + ), + const SizedBox(width: 8), + // Botón eliminar + Tooltip( + message: 'Eliminar negocio', + child: InkWell( + onTap: () { + _showDeleteDialog( + context, + rendererContext.row.cells['id']?.value, + rendererContext.row.cells['nombre']?.value, + ); + }, + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: Colors.red, + borderRadius: BorderRadius.circular(4), + ), + child: const Icon( + Icons.delete, + color: Colors.white, + size: 16, + ), + ), + ), + ), + ], + ); + }, + ), + ], + rows: provider.negociosRows, + onLoaded: (event) { + provider.negociosStateManager = event.stateManager; + }, + createFooter: (stateManager) { + stateManager.setPageSize(10, notify: false); + return PlutoPagination(stateManager); + }, + ); + } + + void _showDeleteDialog( + BuildContext context, String? negocioId, String? nombre) { + if (negocioId == null) return; + + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: AppTheme.of(context).primaryBackground, + title: const Text('Confirmar eliminación'), + content: Text( + '¿Estás seguro de que deseas eliminar la sucursal "$nombre"?'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text( + 'Cancelar', + style: TextStyle(color: AppTheme.of(context).secondaryText), + ), + ), + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + final success = await provider.eliminarNegocio(negocioId); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + success + ? 'Sucursal eliminada correctamente' + : 'Error al eliminar la sucursal', + ), + backgroundColor: success ? Colors.green : Colors.red, + ), + ); + } + }, + child: const Text( + 'Eliminar', + style: TextStyle(color: Colors.red), + ), + ), + ], + ); + }, + ); + } + + String _extraerCiudad(String direccionCompleta) { + // Lógica para extraer la ciudad de la dirección completa + // Suponiendo que la ciudad es la segunda palabra en la dirección + List partes = direccionCompleta.split(','); + return partes.length > 1 ? partes[1].trim() : direccionCompleta; + } +} diff --git a/lib/providers/nethive/componentes_provider.dart b/lib/providers/nethive/componentes_provider.dart index 7d538ec..b1ff469 100644 --- a/lib/providers/nethive/componentes_provider.dart +++ b/lib/providers/nethive/componentes_provider.dart @@ -502,8 +502,15 @@ class ComponentesProvider extends ChangeNotifier { return Container( height: height, width: width, - color: Colors.grey[300], - child: const Icon(Icons.device_unknown), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + ), + child: Image.asset( + 'assets/images/placeholder_no_image.jpg', + height: height, + width: width, + fit: BoxFit.cover, + ), ); } else if (image is Uint8List) { return Image.memory( @@ -511,6 +518,14 @@ class ComponentesProvider extends ChangeNotifier { height: height, width: width, fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + 'assets/images/placeholder_no_image.jpg', + height: height, + width: width, + fit: BoxFit.cover, + ); + }, ); } else if (image is String) { return Image.network( @@ -519,15 +534,20 @@ class ComponentesProvider extends ChangeNotifier { width: width, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { - return Container( + return Image.asset( + 'assets/images/placeholder_no_image.jpg', height: height, width: width, - color: Colors.grey[300], - child: const Icon(Icons.broken_image), + fit: BoxFit.cover, ); }, ); } - return null; + return Image.asset( + 'assets/images/placeholder_no_image.jpg', + height: height, + width: width, + fit: BoxFit.cover, + ); } } diff --git a/lib/providers/nethive/empresas_negocios_provider.dart b/lib/providers/nethive/empresas_negocios_provider.dart index b5e63f3..fdf4ecd 100644 --- a/lib/providers/nethive/empresas_negocios_provider.dart +++ b/lib/providers/nethive/empresas_negocios_provider.dart @@ -120,6 +120,8 @@ class EmpresasNegociosProvider extends ChangeNotifier { 'empresa_id': PlutoCell(value: negocio.empresaId), 'nombre': PlutoCell(value: negocio.nombre), 'direccion': PlutoCell(value: negocio.direccion), + 'direccion_completa': PlutoCell( + value: negocio.direccion), // Nuevo campo para la segunda columna 'latitud': PlutoCell(value: negocio.latitud.toString()), 'longitud': PlutoCell(value: negocio.longitud.toString()), 'tipo_local': PlutoCell(value: negocio.tipoLocal), @@ -340,8 +342,15 @@ class EmpresasNegociosProvider extends ChangeNotifier { return Container( height: height, width: width, - color: Colors.grey[300], - child: const Icon(Icons.image_not_supported), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + ), + child: Image.asset( + 'assets/images/placeholder_no_image.jpg', + height: height, + width: width, + fit: BoxFit.cover, + ), ); } else if (image is Uint8List) { return Image.memory( @@ -349,6 +358,14 @@ class EmpresasNegociosProvider extends ChangeNotifier { height: height, width: width, fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Image.asset( + 'assets/images/placeholder_no_image.jpg', + height: height, + width: width, + fit: BoxFit.cover, + ); + }, ); } else if (image is String) { return Image.network( @@ -357,15 +374,20 @@ class EmpresasNegociosProvider extends ChangeNotifier { width: width, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { - return Container( + return Image.asset( + 'assets/images/placeholder_no_image.jpg', height: height, width: width, - color: Colors.grey[300], - child: const Icon(Icons.broken_image), + fit: BoxFit.cover, ); }, ); } - return null; + return Image.asset( + 'assets/images/placeholder_no_image.jpg', + height: height, + width: width, + fit: BoxFit.cover, + ); } } diff --git a/lib/router/router.dart b/lib/router/router.dart index bc4a3d3..809bb86 100644 --- a/lib/router/router.dart +++ b/lib/router/router.dart @@ -5,6 +5,7 @@ import 'package:go_router/go_router.dart'; import 'package:nethive_neo/helpers/globals.dart'; import 'package:nethive_neo/models/models.dart'; +import 'package:nethive_neo/pages/empresa_negocios/empresa_negocios_page.dart'; import 'package:nethive_neo/pages/pages.dart'; @@ -46,11 +47,7 @@ final GoRouter router = GoRouter( height: MediaQuery.of(context).size.height, child: const Center(child: Text('Book Page Main'))); } else { - return Container( - color: Color.fromARGB(255, 7, 27, 180), - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.height, - child: const Center(child: Text('Book Page Main'))); + return const EmpresaNegociosPage(); } }, ),