empresas y negocios selector integrado

This commit is contained in:
Abraham
2025-07-16 15:48:39 -07:00
parent 9488188de5
commit 8670f90068
13 changed files with 2884 additions and 48 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -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<String> 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);
},
),
);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -52,37 +52,6 @@ void main() async {
);
}
/* class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State<MyApp> createState() => _MyAppState();
static _MyAppState of(BuildContext context) =>
context.findAncestorStateOfType<_MyAppState>()!;
}
class _MyAppState extends State<MyApp> {
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);

View File

@@ -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<EmpresaNegociosPage> createState() => _EmpresaNegociosPageState();
}
class _EmpresaNegociosPageState extends State<EmpresaNegociosPage> {
bool showMapView = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.of(context).primaryBackground,
body: Consumer<EmpresasNegociosProvider>(
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,
),
],
),
),
);
}
}

View File

@@ -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<AddEmpresaDialog> createState() => _AddEmpresaDialogState();
}
class _AddEmpresaDialogState extends State<AddEmpresaDialog> {
final _formKey = GlobalKey<FormState>();
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<Color>(Colors.white),
),
)
: const Text('Crear Empresa'),
),
],
);
}
Future<void> _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;
});
}
}
}
}

View File

@@ -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<AddNegocioDialog> createState() => _AddNegocioDialogState();
}
class _AddNegocioDialogState extends State<AddNegocioDialog> {
final _formKey = GlobalKey<FormState>();
final _nombreController = TextEditingController();
final _direccionController = TextEditingController();
final _latitudController = TextEditingController();
final _longitudController = TextEditingController();
final _tipoLocalController = TextEditingController(text: 'Sucursal');
bool _isLoading = false;
final List<String> _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<String>(
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<String>(
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<Color>(Colors.white),
),
)
: const Text('Crear Sucursal'),
),
],
);
}
Future<void> _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;
});
}
}
}
}

View File

@@ -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),
),
),
),
),
],
),
),
],
],
),
);
}
}

View File

@@ -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<String> partes = direccionCompleta.split(',');
return partes.length > 1 ? partes[1].trim() : direccionCompleta;
}
}

View File

@@ -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,
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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();
}
},
),