referencias añadidas reparada forma de topologia

This commit is contained in:
Abraham
2025-07-30 22:29:28 -07:00
parent 730754930d
commit 8bbe7a53ff
16 changed files with 1959 additions and 4188 deletions

View File

@@ -1,965 +0,0 @@
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: 7.7 KiB

View File

@@ -0,0 +1,92 @@
CREATE OR REPLACE FUNCTION nethive.fn_topologia_por_negocio(p_negocio_id uuid)
RETURNS jsonb
LANGUAGE plpgsql
AS $$
BEGIN
RETURN (
SELECT jsonb_build_object(
'componentes', (
SELECT jsonb_agg(row_to_json(c))
FROM (
SELECT
comp.id,
comp.nombre,
comp.categoria_id,
cat.nombre AS categoria,
comp.rol_logico_id,
r.nombre AS rol_logico,
comp.descripcion,
comp.ubicacion,
comp.imagen_url,
comp.en_uso,
comp.activo,
comp.fecha_registro,
comp.distribucion_id,
td.nombre AS tipo_distribucion,
d.nombre AS nombre_distribucion
FROM nethive.componente comp
LEFT JOIN nethive.categoria_componente cat ON comp.categoria_id = cat.id
LEFT JOIN nethive.rol_logico_componente r ON comp.rol_logico_id = r.id
LEFT JOIN nethive.distribucion d ON comp.distribucion_id = d.id
LEFT JOIN nethive.tipo_distribucion td ON d.tipo_id = td.id
WHERE comp.negocio_id = p_negocio_id
) AS c
),
'conexiones_datos', (
SELECT jsonb_agg(row_to_json(cd))
FROM (
SELECT
cc.id,
cc.componente_origen_id,
co.nombre AS nombre_origen,
ro.id AS rol_logico_origen_id,
ro.nombre AS rol_logico_origen,
cc.componente_destino_id,
cd.nombre AS nombre_destino,
rd.id AS rol_logico_destino_id,
rd.nombre AS rol_logico_destino,
cc.cable_id,
cb.nombre AS nombre_cable,
cc.descripcion,
cc.activo
FROM nethive.conexion_componente cc
LEFT JOIN nethive.componente co ON cc.componente_origen_id = co.id
LEFT JOIN nethive.rol_logico_componente ro ON co.rol_logico_id = ro.id
LEFT JOIN nethive.componente cd ON cc.componente_destino_id = cd.id
LEFT JOIN nethive.rol_logico_componente rd ON cd.rol_logico_id = rd.id
LEFT JOIN nethive.componente cb ON cc.cable_id = cb.id
WHERE co.negocio_id = p_negocio_id OR cd.negocio_id = p_negocio_id
) AS cd
),
'conexiones_energia', (
SELECT jsonb_agg(row_to_json(ce))
FROM (
SELECT
ca.id,
ca.origen_id,
co.nombre AS nombre_origen,
ro.id AS rol_logico_origen_id,
ro.nombre AS rol_logico_origen,
ca.destino_id,
cd.nombre AS nombre_destino,
rd.id AS rol_logico_destino_id,
rd.nombre AS rol_logico_destino,
ca.cable_id,
cb.nombre AS nombre_cable,
ca.descripcion,
ca.activo
FROM nethive.conexion_alimentacion ca
LEFT JOIN nethive.componente co ON ca.origen_id = co.id
LEFT JOIN nethive.rol_logico_componente ro ON co.rol_logico_id = ro.id
LEFT JOIN nethive.componente cd ON ca.destino_id = cd.id
LEFT JOIN nethive.rol_logico_componente rd ON cd.rol_logico_id = rd.id
LEFT JOIN nethive.componente cb ON ca.cable_id = cb.id
WHERE co.negocio_id = p_negocio_id OR cd.negocio_id = p_negocio_id
) AS ce
)
)
);
END;
$$;

View File

@@ -1,294 +0,0 @@
# 📘 Estructura de Base de Datos — NETHIVE (Esquema: `nethive`)
---
## 🏢 `empresa`
| Columna | Tipo de dato | Descripción |
|-----------------|----------------|-----------------------------------|
| id | UUID | PRIMARY KEY |
| nombre | TEXT | Nombre de la empresa |
| rfc | TEXT | Registro fiscal |
| direccion | TEXT | Dirección |
| telefono | TEXT | Teléfono de contacto |
| email | TEXT | Correo electrónico |
| fecha_creacion | TIMESTAMP | Fecha de creación (default: now) |
| logo_url | TEXT | URL del logo |
| imagen_url | TEXT | URL de imagen principal |
---
## 🏪 `negocio`
| Columna | Tipo de dato | Descripción |
|-----------------|----------------|-----------------------------------|
| id | UUID | PRIMARY KEY |
| empresa_id | UUID | FK → empresa.id |
| nombre | TEXT | Nombre del negocio |
| direccion | TEXT | Dirección |
| latitud | DECIMAL(9,6) | Latitud geográfica |
| longitud | DECIMAL(9,6) | Longitud geográfica |
| tipo_local | TEXT | Tipo de local (Sucursal, etc.) |
| fecha_creacion | TIMESTAMP | Default: now() |
| logo_url | TEXT | Logo del negocio |
| imagen_url | TEXT | Imagen del negocio |
---
## 🧾 `categoria_componente`
| Columna | Tipo de dato | Descripción |
|---------|--------------|--------------------------|
| id | SERIAL | PRIMARY KEY |
| nombre | TEXT | Nombre único de categoría|
---
## 📦 `componente`
| Columna | Tipo de dato | Descripción |
|-----------------|----------------|------------------------------------|
| id | UUID | PRIMARY KEY |
| negocio_id | UUID | FK → negocio.id |
| categoria_id | INT | FK → categoria_componente.id |
| nombre | TEXT | Nombre del componente |
| descripcion | TEXT | Descripción general |
| en_uso | BOOLEAN | Si está en uso |
| activo | BOOLEAN | Si está activo |
| ubicacion | TEXT | Ubicación física (rack, bandeja) |
| imagen_url | TEXT | URL de imagen |
| fecha_registro | TIMESTAMP | Default: now() |
| distribucion_id | UUID | FK → distribucion.id |
---
## 🔌 `detalle_cable`
| Columna | Tipo de dato |
|----------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo_cable | TEXT |
| color | TEXT |
| tamaño | DECIMAL(5,2) |
| tipo_conector | TEXT |
---
## 📶 `detalle_switch`
| Columna | Tipo de dato |
|----------------------|----------------|
| componente_id | UUID (PK, FK) |
| marca | TEXT |
| modelo | TEXT |
| numero_serie | TEXT |
| administrable | BOOLEAN |
| poe | BOOLEAN |
| cantidad_puertos | INT |
| velocidad_puertos | TEXT |
| tipo_puertos | TEXT |
| ubicacion_en_rack | TEXT |
| direccion_ip | TEXT |
| firmware | TEXT |
---
## 🧱 `detalle_patch_panel`
| Columna | Tipo de dato |
|---------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo_conector | TEXT |
| numero_puertos | INT |
| categoria | TEXT |
| tipo_montaje | TEXT |
| numeracion_frontal | BOOLEAN |
| panel_ciego | BOOLEAN |
---
## 🗄 `detalle_rack`
| Columna | Tipo de dato |
|------------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| altura_u | INT |
| profundidad_cm | INT |
| ancho_cm | INT |
| ventilacion_integrada | BOOLEAN |
| puertas_con_llave | BOOLEAN |
| ruedas | BOOLEAN |
| color | TEXT |
---
## 🧰 `detalle_organizador`
| Columna | Tipo de dato |
|--------------|----------------|
| componente_id| UUID (PK, FK) |
| tipo | TEXT |
| material | TEXT |
| tamaño | TEXT |
| color | TEXT |
---
## ⚡ `detalle_ups`
| Columna | Tipo de dato |
|--------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| marca | TEXT |
| modelo | TEXT |
| voltaje_entrada | TEXT |
| voltaje_salida | TEXT |
| capacidad_va | INT |
| autonomia_minutos | INT |
| cantidad_tomas | INT |
| rackeable | BOOLEAN |
---
## 🔐 `detalle_router_firewall`
| Columna | Tipo de dato |
|--------------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| marca | TEXT |
| modelo | TEXT |
| numero_serie | TEXT |
| interfaces | TEXT |
| capacidad_routing_gbps | DECIMAL(5,2) |
| direccion_ip | TEXT |
| firmware | TEXT |
| licencias | TEXT |
---
## 🧿 `detalle_equipo_activo`
| Columna | Tipo de dato |
|-------------------|----------------|
| componente_id | UUID (PK, FK) |
| tipo | TEXT |
| marca | TEXT |
| modelo | TEXT |
| numero_serie | TEXT |
| especificaciones | TEXT |
| direccion_ip | TEXT |
| firmware | TEXT |
---
## 🧭 `distribucion`
| Columna | Tipo de dato | Descripción |
|--------------|----------------|--------------------------------------|
| id | UUID | PRIMARY KEY |
| negocio_id | UUID | FK → negocio.id |
| tipo | TEXT | 'MDF' o 'IDF' |
| nombre | TEXT | Nombre de la ubicación lógica |
| descripcion | TEXT | Detalles adicionales (opcional) |
---
## 🔗 `conexion_componente`
| Columna | Tipo de dato | Descripción |
|-----------------------|----------------|------------------------------------------|
| id | UUID | PRIMARY KEY |
| componente_origen_id | UUID | FK → componente.id |
| componente_destino_id | UUID | FK → componente.id |
| descripcion | TEXT | Descripción de la conexión (opcional) |
| activo | BOOLEAN | Si la conexión está activa |
---
## 👁️ `vista_negocios_con_coordenadas`
| Columna | Tipo de dato | Descripción |
|--------------------|--------------|--------------------------------------------|
| negocio_id | UUID | ID del negocio |
| nombre_negocio | TEXT | Nombre del negocio |
| latitud | DECIMAL | Latitud del negocio |
| longitud | DECIMAL | Longitud del negocio |
| logo_negocio | TEXT | URL del logo del negocio |
| imagen_negocio | TEXT | URL de la imagen del negocio |
| empresa_id | UUID | ID de la empresa |
| nombre_empresa | TEXT | Nombre de la empresa |
| logo_empresa | TEXT | URL del logo de la empresa |
| imagen_empresa | TEXT | URL de la imagen de la empresa |
---
## 📋 `vista_inventario_por_negocio`
| Columna | Tipo de dato | Descripción |
|--------------------|--------------|---------------------------------------------|
| componente_id | UUID | ID del componente |
| nombre_componente | TEXT | Nombre del componente |
| categoria | TEXT | Categoría del componente |
| en_uso | BOOLEAN | Si está en uso |
| activo | BOOLEAN | Si está activo |
| ubicacion | TEXT | Ubicación física del componente |
| imagen_componente | TEXT | Imagen asociada al componente |
| negocio_id | UUID | ID del negocio |
| nombre_negocio | TEXT | Nombre del negocio |
| logo_negocio | TEXT | Logo del negocio |
| imagen_negocio | TEXT | Imagen del negocio |
| empresa_id | UUID | ID de la empresa |
| nombre_empresa | TEXT | Nombre de la empresa |
| logo_empresa | TEXT | Logo de la empresa |
| imagen_empresa | TEXT | Imagen de la empresa |
---
## 🧵 `vista_detalle_cables`
| Columna | Tipo de dato | Descripción |
|--------------------|--------------|--------------------------------------------|
| componente_id | UUID | ID del componente |
| nombre | TEXT | Nombre del cable |
| tipo_cable | TEXT | Tipo de cable (UTP, fibra, etc.) |
| color | TEXT | Color del cable |
| tamaño | DECIMAL | Longitud del cable |
| tipo_conector | TEXT | Tipo de conector (RJ45, LC, etc.) |
| en_uso | BOOLEAN | Si está en uso |
| activo | BOOLEAN | Si está activo |
| ubicacion | TEXT | Ubicación física |
| imagen_componente | TEXT | Imagen del cable |
| nombre_negocio | TEXT | Nombre del negocio |
| logo_negocio | TEXT | Logo del negocio |
| nombre_empresa | TEXT | Nombre de la empresa |
| logo_empresa | TEXT | Logo de la empresa |
---
## 📊 `vista_resumen_componentes_activos`
| Columna | Tipo de dato | Descripción |
|------------------|--------------|----------------------------------------------|
| nombre_empresa | TEXT | Nombre de la empresa |
| nombre_negocio | TEXT | Nombre del negocio |
| categoria | TEXT | Categoría del componente |
| cantidad_activos | INTEGER | Cantidad total de componentes activos |
---
## 🔌 `vista_conexiones_por_negocio`
| Columna | Tipo de dato | Descripción |
|-----------------------|--------------|------------------------------------------|
| id | UUID | ID de la conexión |
| componente_origen_id | UUID | Componente origen |
| componente_destino_id | UUID | Componente destino |
| descripcion | TEXT | Descripción de la conexión |
| activo | BOOLEAN | Si la conexión está activa |
"""

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -161,6 +161,25 @@ tipo (text),
nombre (text),
descripcion (text),
-- conexion_alimentacion --
id (uuid) (PK),
origen_id (uuid) (FK de nethive.componente.id),
destino_id (uuid) (FK de nethive.componente.id),
cable_id (uuid) (FK de nethive.componente.id, opcional),
descripcion (text),
activo (bool)
-- rol_logico_componente --
id (serial) (PK),
nombre (text),
descripcion (text)
-- tipo_distribucion --
id (serial) (PK),
nombre (text)
******* VISTAS: *******
@@ -174,7 +193,7 @@ tamaño (numeric),
tipo_conector (text),
conexion_id (uuid),
-- vista_conexiones_por_cables --
-- vista_conexiones_con_cables --
conexion_id (uuid),
descripcion (text),
@@ -275,3 +294,21 @@ ubicacion (text),
imagen_url (text),
fecha_registro (timestamp),
-- vista_alimentacion_componentes --
id (uuid),
origen_id (uuid),
nombre_origen (text),
categoria_origen (int4),
destino_id (uuid),
nombre_destino (text),
categoria_destino (int4),
cable_id (uuid),
nombre_cable (text),
categoria_cable (int4),
descripcion (text),
activo (bool)

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff