multiples mejoras añadidas, se retiró la categoría
This commit is contained in:
@@ -46,7 +46,7 @@ PlutoGridScrollbarConfig plutoGridScrollbarConfig(BuildContext context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PlutoGridStyleConfig plutoGridStyleConfig(BuildContext context,
|
PlutoGridStyleConfig plutoGridStyleConfig(BuildContext context,
|
||||||
{double rowHeight = 100}) {
|
{double rowHeight = 125}) {
|
||||||
return AppTheme.themeMode == ThemeMode.light
|
return AppTheme.themeMode == ThemeMode.light
|
||||||
? PlutoGridStyleConfig(
|
? PlutoGridStyleConfig(
|
||||||
menuBackgroundColor: AppTheme.of(context).secondaryBackground,
|
menuBackgroundColor: AppTheme.of(context).secondaryBackground,
|
||||||
|
|||||||
@@ -64,21 +64,8 @@ class _DashboardPageState extends State<DashboardPage>
|
|||||||
const Gap(24),
|
const Gap(24),
|
||||||
_buildStatsCards(isMobile),
|
_buildStatsCards(isMobile),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
if (!isMobile) ...[
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(child: _buildCategoryChart()),
|
|
||||||
const Gap(24),
|
|
||||||
Expanded(child: _buildRecentActivity()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
] else ...[
|
|
||||||
_buildCategoryChart(),
|
|
||||||
const Gap(24),
|
|
||||||
_buildRecentActivity(),
|
_buildRecentActivity(),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -197,13 +184,6 @@ class _DashboardPageState extends State<DashboardPage>
|
|||||||
AppTheme.of(context).secondaryColor,
|
AppTheme.of(context).secondaryColor,
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
_buildStatCard(
|
|
||||||
'Categorías',
|
|
||||||
stats['total_categories']?.toString() ?? '0',
|
|
||||||
Icons.category,
|
|
||||||
AppTheme.of(context).tertiaryColor,
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
_buildStatCard(
|
_buildStatCard(
|
||||||
'Video más visto',
|
'Video más visto',
|
||||||
stats['most_viewed_video']?['title'] ?? 'N/A',
|
stats['most_viewed_video']?['title'] ?? 'N/A',
|
||||||
@@ -232,15 +212,6 @@ class _DashboardPageState extends State<DashboardPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(16),
|
const Gap(16),
|
||||||
Expanded(
|
|
||||||
child: _buildStatCard(
|
|
||||||
'Categorías',
|
|
||||||
stats['total_categories']?.toString() ?? '0',
|
|
||||||
Icons.category,
|
|
||||||
AppTheme.of(context).tertiaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildStatCard(
|
child: _buildStatCard(
|
||||||
'Video más visto',
|
'Video más visto',
|
||||||
@@ -409,231 +380,6 @@ class _DashboardPageState extends State<DashboardPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCategoryChart() {
|
|
||||||
final categoriesMap = stats['videos_by_category'] as Map<String, dynamic>?;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
begin: Alignment.topLeft,
|
|
||||||
end: Alignment.bottomRight,
|
|
||||||
colors: [
|
|
||||||
AppTheme.of(context).secondaryBackground,
|
|
||||||
AppTheme.of(context).tertiaryBackground,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.2),
|
|
||||||
width: 2,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
blurRadius: 20,
|
|
||||||
offset: const Offset(0, 8),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.circular(24),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(28),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
AppTheme.of(context).primaryColor.withOpacity(0.2),
|
|
||||||
AppTheme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color:
|
|
||||||
AppTheme.of(context).primaryColor.withOpacity(0.3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.pie_chart_rounded,
|
|
||||||
color: AppTheme.of(context).primaryColor,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Distribución por Categoría',
|
|
||||||
style: AppTheme.of(context).title3.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).primaryText,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
'Videos organizados por categoría',
|
|
||||||
style: AppTheme.of(context).bodyText2.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).tertiaryText,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(24),
|
|
||||||
if (categoriesMap != null && categoriesMap.isNotEmpty)
|
|
||||||
...categoriesMap.entries.map(
|
|
||||||
(entry) => Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 16),
|
|
||||||
child: _buildCategoryBar(
|
|
||||||
entry.key,
|
|
||||||
entry.value,
|
|
||||||
categoriesMap.values.reduce((a, b) => a > b ? a : b),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
Center(
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(40),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.category_outlined,
|
|
||||||
size: 64,
|
|
||||||
color: AppTheme.of(context).tertiaryText,
|
|
||||||
),
|
|
||||||
const Gap(16),
|
|
||||||
Text(
|
|
||||||
'No hay datos de categorías',
|
|
||||||
style: AppTheme.of(context).bodyText1.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).tertiaryText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildCategoryBar(String category, int count, int maxCount) {
|
|
||||||
final percentage = maxCount > 0 ? count / maxCount : 0.0;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.of(context).primaryBackground.withOpacity(0.5),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
category,
|
|
||||||
style: AppTheme.of(context).bodyText1.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).primaryText,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
AppTheme.of(context).primaryColor,
|
|
||||||
AppTheme.of(context).secondaryColor,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.3),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
count.toString(),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
height: 10,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.of(context).tertiaryBackground,
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FractionallySizedBox(
|
|
||||||
widthFactor: percentage,
|
|
||||||
child: Container(
|
|
||||||
height: 10,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
AppTheme.of(context).primaryColor,
|
|
||||||
AppTheme.of(context).secondaryColor,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(5),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color:
|
|
||||||
AppTheme.of(context).primaryColor.withOpacity(0.5),
|
|
||||||
blurRadius: 8,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildRecentActivity() {
|
Widget _buildRecentActivity() {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|||||||
@@ -214,55 +214,43 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
// Vista desktop: diseño original con icono, texto y botón
|
// Vista desktop: contador de videos y botón (sin título redundante)
|
||||||
Row(
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
const Color(0xFF4EC9F5),
|
const Color(0xFF4EC9F5).withOpacity(0.15),
|
||||||
const Color(0xFFFFB733),
|
const Color(0xFFFFB733).withOpacity(0.15),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(10),
|
||||||
boxShadow: [
|
border: Border.all(
|
||||||
BoxShadow(
|
color: AppTheme.of(context)
|
||||||
color:
|
.primaryColor
|
||||||
AppTheme.of(context).primaryColor.withOpacity(0.3),
|
.withOpacity(0.2),
|
||||||
blurRadius: 8,
|
width: 1.5,
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: const Icon(
|
|
||||||
Icons.video_library,
|
|
||||||
color: Color(0xFF0B0B0D),
|
|
||||||
size: 24,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(16),
|
child: Icon(
|
||||||
Expanded(
|
Icons.video_collection_rounded,
|
||||||
child: Column(
|
color: AppTheme.of(context).primaryColor,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
size: 20,
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Gestor de Videos',
|
|
||||||
style: AppTheme.of(context).title2.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).primaryText,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 22,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(12),
|
||||||
Text(
|
Text(
|
||||||
'${provider.mediaFiles.length} videos disponibles',
|
'${provider.mediaFiles.length} videos disponibles',
|
||||||
style: AppTheme.of(context).bodyText2.override(
|
style: AppTheme.of(context).bodyText1.override(
|
||||||
fontFamily: 'Poppins',
|
fontFamily: 'Poppins',
|
||||||
color: AppTheme.of(context).tertiaryText,
|
color: AppTheme.of(context).secondaryText,
|
||||||
fontSize: 13,
|
fontSize: 15,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -334,6 +322,7 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
field: 'thumbnail',
|
field: 'thumbnail',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
width: 150,
|
width: 150,
|
||||||
|
enableEditingMode: false,
|
||||||
enableColumnDrag: false,
|
enableColumnDrag: false,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableContextMenu: false,
|
enableContextMenu: false,
|
||||||
@@ -411,55 +400,66 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
title: 'Título',
|
title: 'Título',
|
||||||
field: 'title',
|
field: 'title',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
width: 350,
|
width: 300,
|
||||||
|
enableEditingMode: false,
|
||||||
|
renderer: (rendererContext) {
|
||||||
|
final title = rendererContext.cell.value?.toString() ?? '';
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppTheme.of(context).primaryText,
|
||||||
|
letterSpacing: 0.2,
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
PlutoColumn(
|
PlutoColumn(
|
||||||
title: 'Descripción',
|
title: 'Descripción',
|
||||||
field: 'file_description',
|
field: 'file_description',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
width: 400,
|
width: 425,
|
||||||
),
|
enableEditingMode: false,
|
||||||
PlutoColumn(
|
|
||||||
title: 'Categoría',
|
|
||||||
field: 'category',
|
|
||||||
type: PlutoColumnType.text(),
|
|
||||||
width: 160,
|
|
||||||
renderer: (rendererContext) {
|
renderer: (rendererContext) {
|
||||||
final category = rendererContext.cell.value?.toString() ?? '';
|
final description = rendererContext.cell.value?.toString() ?? '';
|
||||||
if (category.isEmpty) return const SizedBox();
|
if (description.isEmpty) {
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
child: Container(
|
alignment: Alignment.centerLeft,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: LinearGradient(
|
|
||||||
colors: [
|
|
||||||
AppTheme.of(context).primaryColor,
|
|
||||||
AppTheme.of(context).primaryColor.withOpacity(0.7),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.3),
|
|
||||||
blurRadius: 6,
|
|
||||||
offset: const Offset(0, 2),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Text(
|
child: Text(
|
||||||
category.toUpperCase(),
|
'Sin descripción',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
fontFamily: 'Poppins',
|
||||||
fontSize: 10,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w700,
|
fontStyle: FontStyle.italic,
|
||||||
letterSpacing: 0.5,
|
color: AppTheme.of(context).tertiaryText,
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
description,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: AppTheme.of(context).secondaryText,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -468,6 +468,7 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
field: 'reproducciones',
|
field: 'reproducciones',
|
||||||
type: PlutoColumnType.number(),
|
type: PlutoColumnType.number(),
|
||||||
width: 160,
|
width: 160,
|
||||||
|
enableEditingMode: false,
|
||||||
textAlign: PlutoColumnTextAlign.center,
|
textAlign: PlutoColumnTextAlign.center,
|
||||||
renderer: (rendererContext) {
|
renderer: (rendererContext) {
|
||||||
final count = rendererContext.cell.value ?? 0;
|
final count = rendererContext.cell.value ?? 0;
|
||||||
@@ -512,18 +513,72 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
field: 'duration',
|
field: 'duration',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
width: 180,
|
width: 180,
|
||||||
|
enableEditingMode: false,
|
||||||
),
|
),
|
||||||
PlutoColumn(
|
PlutoColumn(
|
||||||
title: 'Fecha de Creación',
|
title: 'Fecha de Creación',
|
||||||
field: 'createdAt',
|
field: 'createdAt',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
width: 180,
|
width: 280,
|
||||||
|
enableEditingMode: false,
|
||||||
|
renderer: (rendererContext) {
|
||||||
|
final video =
|
||||||
|
rendererContext.row.cells['video']?.value as MediaFileModel?;
|
||||||
|
if (video?.createdAt == null) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Text(
|
||||||
|
'Fecha no disponible',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 13,
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
color: AppTheme.of(context).tertiaryText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final formattedDate = _formatDescriptiveDate(video!.createdAt!);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
formattedDate['date']!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
color: AppTheme.of(context).primaryText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
formattedDate['time']!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: AppTheme.of(context).secondaryText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
PlutoColumn(
|
PlutoColumn(
|
||||||
title: 'Etiquetas',
|
title: 'Etiquetas',
|
||||||
field: 'tags',
|
field: 'tags',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
width: 250,
|
width: 250,
|
||||||
|
enableEditingMode: false,
|
||||||
renderer: (rendererContext) {
|
renderer: (rendererContext) {
|
||||||
final video =
|
final video =
|
||||||
rendererContext.row.cells['video']?.value as MediaFileModel?;
|
rendererContext.row.cells['video']?.value as MediaFileModel?;
|
||||||
@@ -563,6 +618,7 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
field: 'actions',
|
field: 'actions',
|
||||||
type: PlutoColumnType.text(),
|
type: PlutoColumnType.text(),
|
||||||
width: 160,
|
width: 160,
|
||||||
|
enableEditingMode: false,
|
||||||
enableColumnDrag: false,
|
enableColumnDrag: false,
|
||||||
enableSorting: false,
|
enableSorting: false,
|
||||||
enableContextMenu: false,
|
enableContextMenu: false,
|
||||||
@@ -605,12 +661,12 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
rows: provider.videosRows,
|
rows: provider.videosRows,
|
||||||
onLoaded: (PlutoGridOnLoadedEvent event) {
|
onLoaded: (PlutoGridOnLoadedEvent event) {
|
||||||
_stateManager = event.stateManager;
|
_stateManager = event.stateManager;
|
||||||
_stateManager!.setShowColumnFilter(true);
|
_stateManager!.setShowColumnFilter(false);
|
||||||
},
|
},
|
||||||
configuration: PlutoGridConfiguration(
|
configuration: PlutoGridConfiguration(
|
||||||
style: plutoGridStyleConfig(context),
|
style: plutoGridStyleConfig(context),
|
||||||
columnSize: const PlutoGridColumnSizeConfig(
|
columnSize: const PlutoGridColumnSizeConfig(
|
||||||
autoSizeMode: PlutoAutoSizeMode.none,
|
autoSizeMode: PlutoAutoSizeMode.scale,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -1174,4 +1230,51 @@ class _GestorVideosPageState extends State<GestorVideosPage> {
|
|||||||
return '${secs}s';
|
return '${secs}s';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, String> _formatDescriptiveDate(DateTime date) {
|
||||||
|
// Obtener día de la semana en español
|
||||||
|
final diasSemana = [
|
||||||
|
'lunes',
|
||||||
|
'martes',
|
||||||
|
'miércoles',
|
||||||
|
'jueves',
|
||||||
|
'viernes',
|
||||||
|
'sábado',
|
||||||
|
'domingo'
|
||||||
|
];
|
||||||
|
final diaSemana = diasSemana[date.weekday - 1];
|
||||||
|
|
||||||
|
// Obtener mes en español
|
||||||
|
final meses = [
|
||||||
|
'enero',
|
||||||
|
'febrero',
|
||||||
|
'marzo',
|
||||||
|
'abril',
|
||||||
|
'mayo',
|
||||||
|
'junio',
|
||||||
|
'julio',
|
||||||
|
'agosto',
|
||||||
|
'septiembre',
|
||||||
|
'octubre',
|
||||||
|
'noviembre',
|
||||||
|
'diciembre'
|
||||||
|
];
|
||||||
|
final mes = meses[date.month - 1];
|
||||||
|
|
||||||
|
// Formatear la hora
|
||||||
|
final hora = date.hour;
|
||||||
|
final minuto = date.minute.toString().padLeft(2, '0');
|
||||||
|
final segundo = date.second.toString().padLeft(2, '0');
|
||||||
|
final periodo = hora >= 12 ? 'pm' : 'am';
|
||||||
|
final hora12 = hora > 12 ? hora - 12 : (hora == 0 ? 12 : hora);
|
||||||
|
|
||||||
|
// Construir las cadenas
|
||||||
|
final fechaTexto = '$diaSemana ${date.day} de $mes del ${date.year}';
|
||||||
|
final horaTexto = 'a las $hora12:$minuto:$segundo $periodo';
|
||||||
|
|
||||||
|
return {
|
||||||
|
'date': fechaTexto,
|
||||||
|
'time': horaTexto,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,31 +79,22 @@ class _PremiumDashboardPageState extends State<PremiumDashboardPage>
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildWelcomeHeader(),
|
/* _buildWelcomeHeader(),
|
||||||
const Gap(32),
|
const Gap(32), */
|
||||||
_buildStatsCards(isMobile),
|
_buildStatsCards(isMobile),
|
||||||
const Gap(32),
|
const Gap(32),
|
||||||
if (!isMobile) ...[
|
if (!isMobile) ...[
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(flex: 3, child: _buildCategoryChart()),
|
Expanded(flex: 2, child: _buildViewsChart()),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
Expanded(flex: 2, child: _buildTopVideos()),
|
Expanded(flex: 2, child: _buildTopVideos()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
Row(
|
_buildRecentActivity(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(flex: 2, child: _buildViewsChart()),
|
|
||||||
const Gap(24),
|
|
||||||
Expanded(flex: 3, child: _buildRecentActivity()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
] else ...[
|
] else ...[
|
||||||
_buildCategoryChart(),
|
|
||||||
const Gap(24),
|
|
||||||
_buildTopVideos(),
|
_buildTopVideos(),
|
||||||
const Gap(24),
|
const Gap(24),
|
||||||
_buildViewsChart(),
|
_buildViewsChart(),
|
||||||
@@ -189,9 +180,8 @@ class _PremiumDashboardPageState extends State<PremiumDashboardPage>
|
|||||||
return _buildLoadingSkeleton(isMobile);
|
return _buildLoadingSkeleton(isMobile);
|
||||||
}
|
}
|
||||||
|
|
||||||
final totalVideos = stats['totalVideos'] ?? 0;
|
final totalVideos = stats['total_videos'] ?? 0;
|
||||||
final totalViews = stats['totalReproducciones'] ?? 0;
|
final totalViews = stats['total_reproducciones'] ?? 0;
|
||||||
final categories = stats['totalCategories'] ?? 0;
|
|
||||||
final avgViews = totalVideos > 0 ? (totalViews / totalVideos).round() : 0;
|
final avgViews = totalVideos > 0 ? (totalViews / totalVideos).round() : 0;
|
||||||
|
|
||||||
final cards = [
|
final cards = [
|
||||||
@@ -215,16 +205,6 @@ class _PremiumDashboardPageState extends State<PremiumDashboardPage>
|
|||||||
trend: '+23%',
|
trend: '+23%',
|
||||||
trendUp: true,
|
trendUp: true,
|
||||||
),
|
),
|
||||||
_StatCard(
|
|
||||||
title: 'Categorías',
|
|
||||||
value: categories.toString(),
|
|
||||||
icon: Icons.category,
|
|
||||||
gradient: const LinearGradient(
|
|
||||||
colors: [Color(0xFF6B2F8A), Color(0xFF9B4FC9)],
|
|
||||||
),
|
|
||||||
trend: '+5%',
|
|
||||||
trendUp: true,
|
|
||||||
),
|
|
||||||
_StatCard(
|
_StatCard(
|
||||||
title: 'Promedio Vistas',
|
title: 'Promedio Vistas',
|
||||||
value: _formatNumber(avgViews),
|
value: _formatNumber(avgViews),
|
||||||
@@ -249,12 +229,12 @@ class _PremiumDashboardPageState extends State<PremiumDashboardPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
return GridView.count(
|
return GridView.count(
|
||||||
crossAxisCount: 4,
|
crossAxisCount: 3,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
crossAxisSpacing: 20,
|
crossAxisSpacing: 20,
|
||||||
mainAxisSpacing: 20,
|
mainAxisSpacing: 20,
|
||||||
childAspectRatio: 1.5,
|
childAspectRatio: 2.2,
|
||||||
children: cards,
|
children: cards,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -264,14 +244,14 @@ class _PremiumDashboardPageState extends State<PremiumDashboardPage>
|
|||||||
baseColor: AppTheme.of(context).tertiaryBackground,
|
baseColor: AppTheme.of(context).tertiaryBackground,
|
||||||
highlightColor: AppTheme.of(context).secondaryBackground,
|
highlightColor: AppTheme.of(context).secondaryBackground,
|
||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
crossAxisCount: isMobile ? 1 : 4,
|
crossAxisCount: isMobile ? 1 : 3,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
crossAxisSpacing: 20,
|
crossAxisSpacing: 20,
|
||||||
mainAxisSpacing: 20,
|
mainAxisSpacing: 20,
|
||||||
childAspectRatio: isMobile ? 3 : 1.5,
|
childAspectRatio: isMobile ? 3 : 1.5,
|
||||||
children: List.generate(
|
children: List.generate(
|
||||||
4,
|
3,
|
||||||
(index) => Container(
|
(index) => Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -283,154 +263,6 @@ class _PremiumDashboardPageState extends State<PremiumDashboardPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCategoryChart() {
|
|
||||||
if (isLoading) {
|
|
||||||
return _buildChartSkeleton('Distribución por Categoría');
|
|
||||||
}
|
|
||||||
|
|
||||||
final videosByCategory =
|
|
||||||
stats['videosByCategory'] as Map<String, int>? ?? {};
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(28),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.of(context).secondaryBackground,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
width: 1,
|
|
||||||
),
|
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.black.withOpacity(0.05),
|
|
||||||
blurRadius: 20,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.all(10),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
Icons.pie_chart,
|
|
||||||
color: AppTheme.of(context).primaryColor,
|
|
||||||
size: 24,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(12),
|
|
||||||
Text(
|
|
||||||
'Distribución por Categoría',
|
|
||||||
style: AppTheme.of(context).title3.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).primaryText,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const Gap(32),
|
|
||||||
SizedBox(
|
|
||||||
height: 280,
|
|
||||||
child: videosByCategory.isEmpty
|
|
||||||
? _buildEmptyChart('No hay datos de categorías')
|
|
||||||
: PieChart(
|
|
||||||
PieChartData(
|
|
||||||
sectionsSpace: 3,
|
|
||||||
centerSpaceRadius: 60,
|
|
||||||
sections: _buildPieChartSections(videosByCategory),
|
|
||||||
borderData: FlBorderData(show: false),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(24),
|
|
||||||
_buildLegend(videosByCategory),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<PieChartSectionData> _buildPieChartSections(
|
|
||||||
Map<String, int> videosByCategory) {
|
|
||||||
final colors = [
|
|
||||||
const Color(0xFF4EC9F5),
|
|
||||||
const Color(0xFFFFB733),
|
|
||||||
const Color(0xFF6B2F8A),
|
|
||||||
const Color(0xFFFF2D2D),
|
|
||||||
const Color(0xFF00C9A7),
|
|
||||||
];
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
return videosByCategory.entries.map((entry) {
|
|
||||||
final color = colors[index % colors.length];
|
|
||||||
index++;
|
|
||||||
|
|
||||||
return PieChartSectionData(
|
|
||||||
value: entry.value.toDouble(),
|
|
||||||
title: '${entry.value}',
|
|
||||||
color: color,
|
|
||||||
radius: 50,
|
|
||||||
titleStyle: const TextStyle(
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: Color(0xFF0B0B0D),
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildLegend(Map<String, int> videosByCategory) {
|
|
||||||
final colors = [
|
|
||||||
const Color(0xFF4EC9F5),
|
|
||||||
const Color(0xFFFFB733),
|
|
||||||
const Color(0xFF6B2F8A),
|
|
||||||
const Color(0xFFFF2D2D),
|
|
||||||
const Color(0xFF00C9A7),
|
|
||||||
];
|
|
||||||
|
|
||||||
int index = 0;
|
|
||||||
return Wrap(
|
|
||||||
spacing: 16,
|
|
||||||
runSpacing: 12,
|
|
||||||
children: videosByCategory.entries.map((entry) {
|
|
||||||
final color = colors[index % colors.length];
|
|
||||||
index++;
|
|
||||||
|
|
||||||
return Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: 16,
|
|
||||||
height: 16,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: color,
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
Text(
|
|
||||||
entry.key,
|
|
||||||
style: AppTheme.of(context).bodyText2.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).secondaryText,
|
|
||||||
fontSize: 13,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildViewsChart() {
|
Widget _buildViewsChart() {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(28),
|
padding: const EdgeInsets.all(28),
|
||||||
@@ -1008,60 +840,91 @@ class _StatCardState extends State<_StatCard>
|
|||||||
onExit: (_) => setState(() => _isHovered = false),
|
onExit: (_) => setState(() => _isHovered = false),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
transform: Matrix4.identity()..scale(_isHovered ? 1.03 : 1.0),
|
transform: Matrix4.identity()..scale(_isHovered ? 1.02 : 1.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: widget.gradient,
|
gradient: widget.gradient,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: widget.gradient.colors.first.withOpacity(0.3),
|
color: widget.gradient.colors.first.withOpacity(0.25),
|
||||||
blurRadius: _isHovered ? 20 : 12,
|
blurRadius: _isHovered ? 16 : 10,
|
||||||
offset: Offset(0, _isHovered ? 8 : 4),
|
offset: Offset(0, _isHovered ? 6 : 3),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Icono y valor en la izquierda
|
||||||
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
|
// Icono
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(10),
|
padding: const EdgeInsets.all(8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withOpacity(0.2),
|
color: Colors.white.withOpacity(0.25),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
widget.icon,
|
widget.icon,
|
||||||
color: const Color(0xFF0B0B0D),
|
color: const Color(0xFF0B0B0D),
|
||||||
size: 24,
|
size: 22,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const Gap(12),
|
||||||
|
// Valor
|
||||||
|
Text(
|
||||||
|
widget.value,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Color(0xFF0B0B0D),
|
||||||
|
fontSize: 28,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
height: 1.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
// Título
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: const Color(0xFF0B0B0D).withOpacity(0.75),
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Trend badge a la derecha
|
||||||
if (widget.trend != null)
|
if (widget.trend != null)
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8, vertical: 4),
|
horizontal: 10,
|
||||||
|
vertical: 6,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: widget.trendUp
|
color: widget.trendUp
|
||||||
? Colors.green.withOpacity(0.2)
|
? Colors.white.withOpacity(0.3)
|
||||||
: Colors.red.withOpacity(0.2),
|
: Colors.black.withOpacity(0.2),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
widget.trendUp
|
widget.trendUp
|
||||||
? Icons.trending_up
|
? Icons.trending_up
|
||||||
: Icons.trending_down,
|
: Icons.trending_down,
|
||||||
size: 14,
|
size: 18,
|
||||||
color: const Color(0xFF0B0B0D),
|
color: const Color(0xFF0B0B0D),
|
||||||
),
|
),
|
||||||
const Gap(4),
|
const Gap(2),
|
||||||
Text(
|
Text(
|
||||||
widget.trend!,
|
widget.trend!,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
@@ -1076,32 +939,6 @@ class _StatCardState extends State<_StatCard>
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
widget.value,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Color(0xFF0B0B0D),
|
|
||||||
fontSize: 32,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Gap(4),
|
|
||||||
Text(
|
|
||||||
widget.title,
|
|
||||||
style: TextStyle(
|
|
||||||
color: const Color(0xFF0B0B0D).withOpacity(0.8),
|
|
||||||
fontSize: 14,
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -25,16 +25,19 @@ class _VideosLayoutState extends State<VideosLayout> {
|
|||||||
title: 'Dashboard',
|
title: 'Dashboard',
|
||||||
icon: Icons.dashboard,
|
icon: Icons.dashboard,
|
||||||
index: 0,
|
index: 0,
|
||||||
|
subtitle: 'Visualiza métricas y estadísticas globales de tus contenidos',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
title: 'Gestor de Videos',
|
title: 'Gestor de Videos',
|
||||||
icon: Icons.video_library,
|
icon: Icons.video_library,
|
||||||
index: 1,
|
index: 1,
|
||||||
|
subtitle: 'Administra, edita y organiza tu biblioteca multimedia',
|
||||||
),
|
),
|
||||||
MenuItem(
|
MenuItem(
|
||||||
title: 'Configuración',
|
title: 'Configuración',
|
||||||
icon: Icons.settings,
|
icon: Icons.settings,
|
||||||
index: 2,
|
index: 2,
|
||||||
|
subtitle: 'Personaliza las preferencias de tu plataforma',
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -65,6 +68,7 @@ class _VideosLayoutState extends State<VideosLayout> {
|
|||||||
Widget _buildHeader(bool isMobile) {
|
Widget _buildHeader(bool isMobile) {
|
||||||
final isDark = AppTheme.themeMode == ThemeMode.dark;
|
final isDark = AppTheme.themeMode == ThemeMode.dark;
|
||||||
final isLightBackground = !isDark;
|
final isLightBackground = !isDark;
|
||||||
|
final currentMenuItem = _menuItems[_selectedMenuIndex];
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.all(isMobile ? 16 : 24),
|
padding: EdgeInsets.all(isMobile ? 16 : 24),
|
||||||
@@ -79,30 +83,85 @@ class _VideosLayoutState extends State<VideosLayout> {
|
|||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (isMobile)
|
if (isMobile) ...[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.menu),
|
icon: const Icon(Icons.menu),
|
||||||
color: AppTheme.of(context).primaryText,
|
color: AppTheme.of(context).primaryText,
|
||||||
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
|
onPressed: () => _scaffoldKey.currentState?.openDrawer(),
|
||||||
),
|
),
|
||||||
// Logo de EnergyMedia
|
// Logo de EnergyMedia solo en mobile
|
||||||
Image.asset(
|
Image.asset(
|
||||||
isMobile
|
'assets/images/favicon.png',
|
||||||
? 'assets/images/favicon.png'
|
height: 32,
|
||||||
: isLightBackground
|
|
||||||
? 'assets/images/logo_nh.png'
|
|
||||||
: 'assets/images/logo_nh_b.png',
|
|
||||||
height: isMobile ? 32 : 75,
|
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Text(
|
Text(
|
||||||
_menuItems[_selectedMenuIndex].title,
|
currentMenuItem.title,
|
||||||
style: AppTheme.of(context).bodyText1.override(
|
style: AppTheme.of(context).bodyText1.override(
|
||||||
fontFamily: 'Poppins',
|
fontFamily: 'Poppins',
|
||||||
color: AppTheme.of(context).secondaryText,
|
color: AppTheme.of(context).secondaryText,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
] else ...[
|
||||||
|
// Desktop: título prominente con subtítulo
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [
|
||||||
|
const Color(0xFF4EC9F5).withOpacity(0.15),
|
||||||
|
const Color(0xFFFFB733).withOpacity(0.15),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border: Border.all(
|
||||||
|
color: AppTheme.of(context).primaryColor.withOpacity(0.2),
|
||||||
|
width: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
currentMenuItem.icon,
|
||||||
|
color: AppTheme.of(context).primaryColor,
|
||||||
|
size: 26,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
currentMenuItem.title,
|
||||||
|
style: AppTheme.of(context).title2.override(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
color: AppTheme.of(context).primaryText,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 24,
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (currentMenuItem.subtitle != null) ...[
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
currentMenuItem.subtitle!,
|
||||||
|
style: AppTheme.of(context).bodyText2.override(
|
||||||
|
fontFamily: 'Poppins',
|
||||||
|
color: AppTheme.of(context).tertiaryText,
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -161,7 +220,7 @@ class _VideosLayoutState extends State<VideosLayout> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// Logo con container elegante
|
// Logo con container elegante
|
||||||
Container(
|
Container(
|
||||||
@@ -961,10 +1020,12 @@ class MenuItem {
|
|||||||
final String title;
|
final String title;
|
||||||
final IconData icon;
|
final IconData icon;
|
||||||
final int index;
|
final int index;
|
||||||
|
final String? subtitle;
|
||||||
|
|
||||||
MenuItem({
|
MenuItem({
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.index,
|
required this.index,
|
||||||
|
this.subtitle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ class _EditVideoDialogState extends State<EditVideoDialog> {
|
|||||||
late TextEditingController titleController;
|
late TextEditingController titleController;
|
||||||
late TextEditingController descriptionController;
|
late TextEditingController descriptionController;
|
||||||
late TextEditingController tagsController;
|
late TextEditingController tagsController;
|
||||||
MediaCategoryModel? selectedCategory;
|
|
||||||
Uint8List? newPosterBytes;
|
Uint8List? newPosterBytes;
|
||||||
String? newPosterFileName;
|
String? newPosterFileName;
|
||||||
VideoPlayerController? _videoPlayerController;
|
VideoPlayerController? _videoPlayerController;
|
||||||
@@ -54,9 +53,6 @@ class _EditVideoDialogState extends State<EditVideoDialog> {
|
|||||||
descriptionController =
|
descriptionController =
|
||||||
TextEditingController(text: widget.video.fileDescription);
|
TextEditingController(text: widget.video.fileDescription);
|
||||||
tagsController = TextEditingController(text: widget.video.tags.join(', '));
|
tagsController = TextEditingController(text: widget.video.tags.join(', '));
|
||||||
selectedCategory = widget.provider.categories
|
|
||||||
.where((cat) => cat.mediaCategoriesId == widget.video.mediaCategoryFk)
|
|
||||||
.firstOrNull;
|
|
||||||
_initializeVideoPlayer();
|
_initializeVideoPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,15 +148,6 @@ class _EditVideoDialogState extends State<EditVideoDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar categoría
|
|
||||||
if (selectedCategory != null &&
|
|
||||||
selectedCategory!.mediaCategoriesId != widget.video.mediaCategoryFk) {
|
|
||||||
await widget.provider.updateVideoCategory(
|
|
||||||
widget.video.mediaFileId,
|
|
||||||
selectedCategory!.mediaCategoriesId,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Actualizar tags
|
// Actualizar tags
|
||||||
final newTags = tagsController.text
|
final newTags = tagsController.text
|
||||||
.split(RegExp(r'[,\s]+'))
|
.split(RegExp(r'[,\s]+'))
|
||||||
@@ -346,10 +333,6 @@ class _EditVideoDialogState extends State<EditVideoDialog> {
|
|||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
),
|
),
|
||||||
const Gap(20),
|
const Gap(20),
|
||||||
_buildLabel('Categoría'),
|
|
||||||
const Gap(8),
|
|
||||||
_buildCategoryDropdown(),
|
|
||||||
const Gap(20),
|
|
||||||
_buildLabel('Etiquetas (Tags)'),
|
_buildLabel('Etiquetas (Tags)'),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
@@ -565,45 +548,6 @@ class _EditVideoDialogState extends State<EditVideoDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCategoryDropdown() {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.of(context).tertiaryBackground,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DropdownButtonFormField<MediaCategoryModel>(
|
|
||||||
value: selectedCategory,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: Icon(
|
|
||||||
Icons.category,
|
|
||||||
color: AppTheme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
dropdownColor: AppTheme.of(context).secondaryBackground,
|
|
||||||
items: widget.provider.categories.map((category) {
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: category,
|
|
||||||
child: Text(
|
|
||||||
category.categoryName,
|
|
||||||
style: AppTheme.of(context).bodyText1.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).primaryText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() => selectedCategory = value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildActions() {
|
Widget _buildActions() {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(24),
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ class _PremiumUploadDialogState extends State<PremiumUploadDialog> {
|
|||||||
final titleController = TextEditingController();
|
final titleController = TextEditingController();
|
||||||
final descriptionController = TextEditingController();
|
final descriptionController = TextEditingController();
|
||||||
final tagsController = TextEditingController();
|
final tagsController = TextEditingController();
|
||||||
MediaCategoryModel? selectedCategory;
|
|
||||||
Uint8List? selectedVideo;
|
Uint8List? selectedVideo;
|
||||||
String? videoFileName;
|
String? videoFileName;
|
||||||
Uint8List? selectedPoster;
|
Uint8List? selectedPoster;
|
||||||
@@ -133,7 +132,6 @@ class _PremiumUploadDialogState extends State<PremiumUploadDialog> {
|
|||||||
|
|
||||||
Future<void> _uploadVideo() async {
|
Future<void> _uploadVideo() async {
|
||||||
if (titleController.text.isEmpty ||
|
if (titleController.text.isEmpty ||
|
||||||
selectedCategory == null ||
|
|
||||||
selectedVideo == null ||
|
selectedVideo == null ||
|
||||||
videoFileName == null) {
|
videoFileName == null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
@@ -165,7 +163,6 @@ class _PremiumUploadDialogState extends State<PremiumUploadDialog> {
|
|||||||
description: descriptionController.text.isEmpty
|
description: descriptionController.text.isEmpty
|
||||||
? null
|
? null
|
||||||
: descriptionController.text,
|
: descriptionController.text,
|
||||||
categoryId: selectedCategory!.mediaCategoriesId,
|
|
||||||
tags: tags,
|
tags: tags,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -359,10 +356,6 @@ class _PremiumUploadDialogState extends State<PremiumUploadDialog> {
|
|||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
),
|
),
|
||||||
const Gap(20),
|
const Gap(20),
|
||||||
_buildLabel('Categoría *'),
|
|
||||||
const Gap(8),
|
|
||||||
_buildCategoryDropdown(),
|
|
||||||
const Gap(20),
|
|
||||||
_buildLabel('Etiquetas (Tags)'),
|
_buildLabel('Etiquetas (Tags)'),
|
||||||
const Gap(4),
|
const Gap(4),
|
||||||
Text(
|
Text(
|
||||||
@@ -446,52 +439,6 @@ class _PremiumUploadDialogState extends State<PremiumUploadDialog> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCategoryDropdown() {
|
|
||||||
return Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppTheme.of(context).tertiaryBackground,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppTheme.of(context).primaryColor.withOpacity(0.1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: DropdownButtonFormField<MediaCategoryModel>(
|
|
||||||
value: selectedCategory,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
prefixIcon: Icon(
|
|
||||||
Icons.category,
|
|
||||||
color: AppTheme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: const EdgeInsets.all(16),
|
|
||||||
),
|
|
||||||
hint: Text(
|
|
||||||
'Selecciona una categoría',
|
|
||||||
style: AppTheme.of(context).bodyText1.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).tertiaryText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
dropdownColor: AppTheme.of(context).secondaryBackground,
|
|
||||||
items: widget.provider.categories.map((category) {
|
|
||||||
return DropdownMenuItem(
|
|
||||||
value: category,
|
|
||||||
child: Text(
|
|
||||||
category.categoryName,
|
|
||||||
style: AppTheme.of(context).bodyText1.override(
|
|
||||||
fontFamily: 'Poppins',
|
|
||||||
color: AppTheme.of(context).primaryText,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
onChanged: (value) {
|
|
||||||
setState(() => selectedCategory = value);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildVideoSelector() {
|
Widget _buildVideoSelector() {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: _selectVideo,
|
onTap: _selectVideo,
|
||||||
|
|||||||
@@ -135,8 +135,6 @@ class VideosProvider extends ChangeNotifier {
|
|||||||
'thumbnail': PlutoCell(value: media.fileUrl),
|
'thumbnail': PlutoCell(value: media.fileUrl),
|
||||||
'title': PlutoCell(value: media.title ?? media.fileName),
|
'title': PlutoCell(value: media.title ?? media.fileName),
|
||||||
'file_description': PlutoCell(value: media.fileDescription),
|
'file_description': PlutoCell(value: media.fileDescription),
|
||||||
'category':
|
|
||||||
PlutoCell(value: _getCategoryName(media.mediaCategoryFk)),
|
|
||||||
'reproducciones': PlutoCell(value: media.reproducciones),
|
'reproducciones': PlutoCell(value: media.reproducciones),
|
||||||
'duration': PlutoCell(
|
'duration': PlutoCell(
|
||||||
value: media.seconds != null
|
value: media.seconds != null
|
||||||
@@ -168,18 +166,6 @@ class VideosProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get category name by ID
|
|
||||||
String _getCategoryName(int? categoryId) {
|
|
||||||
if (categoryId == null) return 'Sin categoría';
|
|
||||||
try {
|
|
||||||
return categories
|
|
||||||
.firstWhere((cat) => cat.mediaCategoriesId == categoryId)
|
|
||||||
.categoryName;
|
|
||||||
} catch (e) {
|
|
||||||
return 'Sin categoría';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format file size to human readable
|
/// Format file size to human readable
|
||||||
String _formatFileSize(int? bytes) {
|
String _formatFileSize(int? bytes) {
|
||||||
if (bytes == null) return '-';
|
if (bytes == null) return '-';
|
||||||
@@ -245,7 +231,6 @@ class VideosProvider extends ChangeNotifier {
|
|||||||
Future<bool> uploadVideo({
|
Future<bool> uploadVideo({
|
||||||
required String title,
|
required String title,
|
||||||
String? description,
|
String? description,
|
||||||
int? categoryId,
|
|
||||||
int? durationSeconds,
|
int? durationSeconds,
|
||||||
List<String>? tags,
|
List<String>? tags,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -307,7 +292,6 @@ class VideosProvider extends ChangeNotifier {
|
|||||||
'file_url': videoUrl,
|
'file_url': videoUrl,
|
||||||
'storage_path': videoStoragePath,
|
'storage_path': videoStoragePath,
|
||||||
'organization_fk': organizationId,
|
'organization_fk': organizationId,
|
||||||
'media_category_fk': categoryId,
|
|
||||||
'metadata_json': metadataJson,
|
'metadata_json': metadataJson,
|
||||||
'seconds': durationSeconds,
|
'seconds': durationSeconds,
|
||||||
'is_public_file': true,
|
'is_public_file': true,
|
||||||
@@ -445,25 +429,6 @@ class VideosProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update video category
|
|
||||||
Future<bool> updateVideoCategory(int mediaFileId, int? categoryId) async {
|
|
||||||
try {
|
|
||||||
await supabaseML
|
|
||||||
.from('media_files')
|
|
||||||
.update({'media_category_fk': categoryId})
|
|
||||||
.eq('media_file_id', mediaFileId)
|
|
||||||
.eq('organization_fk', organizationId);
|
|
||||||
|
|
||||||
await loadMediaFiles();
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
errorMessage = 'Error actualizando categoría: $e';
|
|
||||||
notifyListeners();
|
|
||||||
print('Error en updateVideoCategory: $e');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update video metadata
|
/// Update video metadata
|
||||||
Future<bool> updateVideoMetadata(
|
Future<bool> updateVideoMetadata(
|
||||||
int mediaFileId,
|
int mediaFileId,
|
||||||
@@ -689,29 +654,10 @@ class VideosProvider extends ChangeNotifier {
|
|||||||
curr.reproducciones > next.reproducciones ? curr : next);
|
curr.reproducciones > next.reproducciones ? curr : next);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Videos by category
|
|
||||||
Map<String, int> videosByCategory = {};
|
|
||||||
for (var media in mediaFiles) {
|
|
||||||
final categoryName = _getCategoryName(media.mediaCategoryFk);
|
|
||||||
videosByCategory[categoryName] =
|
|
||||||
(videosByCategory[categoryName] ?? 0) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Most viewed category
|
|
||||||
String? mostViewedCategory;
|
|
||||||
if (videosByCategory.isNotEmpty) {
|
|
||||||
mostViewedCategory = videosByCategory.entries
|
|
||||||
.reduce((a, b) => a.value > b.value ? a : b)
|
|
||||||
.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'total_videos': totalVideos,
|
'total_videos': totalVideos,
|
||||||
'total_reproducciones': totalReproducciones,
|
'total_reproducciones': totalReproducciones,
|
||||||
'most_viewed_video': mostViewed?.toMap(),
|
'most_viewed_video': mostViewed?.toMap(),
|
||||||
'videos_by_category': videosByCategory,
|
|
||||||
'most_viewed_category': mostViewedCategory,
|
|
||||||
'total_categories': categories.length,
|
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error en getDashboardStats: $e');
|
print('Error en getDashboardStats: $e');
|
||||||
@@ -745,8 +691,6 @@ class VideosProvider extends ChangeNotifier {
|
|||||||
'thumbnail': PlutoCell(value: media.fileUrl),
|
'thumbnail': PlutoCell(value: media.fileUrl),
|
||||||
'title': PlutoCell(value: media.title ?? media.fileName),
|
'title': PlutoCell(value: media.title ?? media.fileName),
|
||||||
'file_description': PlutoCell(value: media.fileDescription),
|
'file_description': PlutoCell(value: media.fileDescription),
|
||||||
'category':
|
|
||||||
PlutoCell(value: _getCategoryName(media.mediaCategoryFk)),
|
|
||||||
'reproducciones': PlutoCell(value: media.reproducciones),
|
'reproducciones': PlutoCell(value: media.reproducciones),
|
||||||
'duration': PlutoCell(
|
'duration': PlutoCell(
|
||||||
value: media.seconds != null
|
value: media.seconds != null
|
||||||
|
|||||||
Reference in New Issue
Block a user