Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Light Vitae
# SIMEVEC

This is a simple crud application built with Flutter and Firebase.

Expand Down
4 changes: 2 additions & 2 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Light Vitae',
title: 'SIMEVEC',
navigatorKey: NavigationService.navigatorKey,
home: const SplashScreen(),
routes: routes,
// Add localization supportj
// Add localization support
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
Expand Down
11 changes: 0 additions & 11 deletions lib/routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,24 @@ import 'package:client_service/view/auth/login_admin_screen.dart';
import 'package:flutter/cupertino.dart';

final Map<String, WidgetBuilder> routes = {
// Auth
'/splash': (context) => const SplashScreen(),
'/login': (context) => const LoginSelectionScreen(),
'/login-empleado': (context) => const LoginEmpleadoScreen(),
'/login-admin': (context) => const LoginAdminScreen(),
// Registros
'Nuevos Empleados': (context) => const RegistroEmpleadoPage(),
'Nuevos Clientes': (context) => const RegistroClientePage(),

// Servicios
'Registro de instalación': (context) => const RegistroInstalacion(),
'Mantenimiento de cámaras': (context) => const RegistroCamara(),
'Alquiler vehículos': (context) => const RegistroAlquiler(),

// Facturación
'Dashboard Facturación': (context) => const DashboardFacturacion(),
'Nuevas Facturas': (context) => const CreateFactura(),
'Reporte de Facturas': (context) => const FacturasListAvanzada(),
'Anular Factura': (context) => const AnularFacturas(),

// Reportes
'Empleados': (context) => const AsistenciasAdminScreen(),
'Clientes': (context) => const ReportCliente(),
'Reporte de instalaciones': (context) => const ReportInstalacion(),
'Reporte de cámaras': (context) => const ReportCamara(),
'Reporte vehículos': (context) => const ReportVehiculo(),

// Nuevas funcionalidades
// 'calendario': (context) => const CalendarioScreen(), // REMOVED: CalendarioScreen now requires Empleado
'notificaciones': (context) => const NotificacionesScreen(),
'configuracion': (context) => const ConfiguracionScreen(),
};
1 change: 0 additions & 1 deletion lib/services/service_locator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import '../viewmodel/instalacion_viewmodel.dart';
import '../viewmodel/camara_viewmodel.dart';
import '../viewmodel/vehiculo_viewmodel.dart';
import '../viewmodel/factura_viewmodel.dart';
import '../viewmodel/calendario_viewmodel.dart';
import '../services/notificacion_service.dart';

final GetIt sl = GetIt.instance;
Expand Down
4 changes: 2 additions & 2 deletions lib/utils/events/splash_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ class _SplashScreenState extends State<SplashScreen>

// Título principal
Text(
'LIGHT VITAE',
'SIMEVEC',
style: TextStyle(
fontSize: 42,
fontWeight: FontWeight.bold,
Expand Down Expand Up @@ -186,7 +186,7 @@ class _SplashScreenState extends State<SplashScreen>

// Footer
Text(
'© 2025 Light Vitae',
'© 2025 SIMEVEC',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.8),
Expand Down
159 changes: 38 additions & 121 deletions lib/utils/excel_export_utility.dart
Original file line number Diff line number Diff line change
@@ -1,122 +1,46 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:excel/excel.dart';
import 'package:flutter/foundation.dart';
import 'package:universal_html/html.dart' as html;
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:open_file/open_file.dart';

/// Guarda el archivo en la carpeta Descargas y lo abre (no web)
Future<void> saveFileToDownloads(List<int> fileBytes, String fileName) async {
try {
Directory? downloadsDir;
if (Platform.isAndroid) {
downloadsDir = Directory('/storage/emulated/0/Download');
} else if (Platform.isIOS) {
downloadsDir = await getApplicationDocumentsDirectory();
} else {
downloadsDir = await getDownloadsDirectory();
}
if (downloadsDir == null) {
throw Exception('No se pudo obtener la carpeta de descargas.');
}
final file = File('${downloadsDir.path}/$fileName');
await file.writeAsBytes(fileBytes, flush: true);
print('Archivo guardado en: ${file.path}');
await OpenFile.open(file.path);
} catch (e) {
print('Error guardando archivo Excel: $e');
throw Exception('Error guardando archivo Excel: $e');
}
}

/// A custom utility class for exporting Firestore collections to Excel files.
///
/// This utility is designed to be compatible with the latest versions of
/// the excel package and Dart SDK.
class ExcelSheetData {
final String sheetName;
final List<String> headers;
final List<List<dynamic>> rows;
ExcelSheetData(
{required this.sheetName, required this.headers, required this.rows});
ExcelSheetData({
required this.sheetName,
required this.headers,
required this.rows,
});
}

class ExcelExportUtility {
/// Exports a Firestore collection to an Excel file.
///
/// [collectionName] - The name of the Firestore collection to fetch data from.
/// [headers] - The list of headers for the Excel file.
/// [mapper] - A function that maps Firestore document data to a list of values for each row.
/// [sheetName] - The name of the Excel sheet.
/// [fileName] - The name of the resulting Excel file (default: 'export.xlsx').
/// [queryBuilder] - An optional function to customize the Firestore query.
static Future<void> exportToExcel({
required String collectionName,
required List<String> headers,
required List<dynamic> Function(Map<String, dynamic>) mapper,
required String sheetName,
String fileName = 'export.xlsx',
Query Function(Query query)? queryBuilder,
}) async {
try {
// Build Firestore query
Query query = FirebaseFirestore.instance.collection(collectionName);
if (queryBuilder != null) {
query = queryBuilder(query);
}

// Fetch data from Firestore
QuerySnapshot snapshot = await query.get();

if (snapshot.docs.isEmpty) {
throw Exception(
'No data found in the Firestore collection: $collectionName');
}

// Initialize Excel
var excel = Excel.createExcel();
Sheet sheetObject = excel['Sheet1'];

// Create header cell style
CellStyle headerCellStyle = CellStyle(
backgroundColorHex: ExcelColor.blue,
fontFamily: getFontFamily(FontFamily.Calibri),
fontSize: 12,
horizontalAlign: HorizontalAlign.Center,
verticalAlign: VerticalAlign.Center,
bold: true,
);

// Write headers to the first row
for (int i = 0; i < headers.length; i++) {
var cell = sheetObject.cell(
CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0),
);
cell.cellStyle = headerCellStyle;
cell.value = TextCellValue(headers[i]);
}

// Create data cell style
var dataCellStyle = CellStyle(
fontFamily: getFontFamily(FontFamily.Calibri),
fontSize: 12,
horizontalAlign: HorizontalAlign.Left,
verticalAlign: VerticalAlign.Center,
);

// Write data rows
int rowIndex = 1;
for (var doc in snapshot.docs) {
final data = doc.data() as Map<String, dynamic>;
final row = mapper(data);

for (int i = 0; i < row.length; i++) {
var cell = sheetObject.cell(
CellIndex.indexByColumnRow(columnIndex: i, rowIndex: rowIndex),
);
cell.cellStyle = dataCellStyle;
cell.value = row[i] != null
? TextCellValue(row[i].toString())
: TextCellValue('');
}
rowIndex++;
}

// set column widths)
for (int i = 0; i < headers.length; i++) {
sheetObject.setColumnWidth(i, 20.0);
}

// Rename the default sheet to the desired name
excel.rename(sheetObject.sheetName, sheetName);

// Encode Excel file
List<int>? fileBytes = excel.encode();
if (fileBytes == null) {
throw Exception("Failed to encode Excel file.");
}

// Create and trigger download (for web platform)
await _downloadFile(fileBytes, fileName);
} catch (e) {
throw Exception('Error exporting to Excel: $e');
}
}

/// Exporta múltiples hojas a un solo archivo Excel (una hoja por empleado, por ejemplo)
static Future<void> exportMultipleSheets({
required List<ExcelSheetData> sheets,
String fileName = 'export.xlsx',
Expand Down Expand Up @@ -149,37 +73,30 @@ class ExcelExportUtility {
}
List<int>? fileBytes = excel.encode();
if (fileBytes == null) throw Exception("Failed to encode Excel file.");
await _downloadFile(fileBytes, fileName);
if (kIsWeb) {
await _downloadFile(fileBytes, fileName);
} else {
await saveFileToDownloads(fileBytes, fileName);
}
} catch (e) {
throw Exception('Error exporting multiple sheets to Excel: $e');
}
}

/// Downloads the Excel file on web platform
static Future<void> _downloadFile(
List<int> fileBytes, String fileName) async {
try {
// Create a Blob from the bytes
final blob = html.Blob(
[fileBytes],
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
);

// Generate a download URL
final url = html.Url.createObjectUrlFromBlob(blob);

// Create an anchor element and trigger the download
final anchor = html.document.createElement('a') as html.AnchorElement;
anchor.href = url;
anchor.style.display = 'none';
anchor.download = fileName;

html.document.body?.children.add(anchor);

// Trigger the download
anchor.click();

// Clean up by removing the anchor and revoking the URL
anchor.remove();
html.Url.revokeObjectUrl(url);
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions lib/view/asistencia/asistencia_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ class _AsistenciaScreenState extends State<AsistenciaScreen> {
},
)
else if (_entrada != null && _salida != null)
Column(
children: const [
const Column(
children: [
Icon(Icons.check_circle,
color: Colors.green, size: 48),
SizedBox(height: 12),
Expand Down
2 changes: 1 addition & 1 deletion lib/view/auth/login_admin_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class _LoginAdminScreenBodyState extends State<_LoginAdminScreenBody> {
),
const SizedBox(height: 20),
const Text(
'© 2025 Light Vitae',
'© 2025 SIMEVEC',
style: TextStyle(
fontSize: 12,
color: Colors.white70,
Expand Down
42 changes: 35 additions & 7 deletions lib/view/auth/login_empleado_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:client_service/viewmodel/auth_viewmodel.dart';
import 'package:client_service/providers/empleado_provider.dart';
import 'package:client_service/view/auth/cambiar_password_screen.dart';
import 'package:client_service/view/panel_empleado/panel_empleado_screen.dart';
import 'package:client_service/view/auth/login_selection_screen.dart';
import 'package:client_service/view/widgets/auth/login_card.dart';

class LoginEmpleadoScreen extends StatefulWidget {
Expand Down Expand Up @@ -64,12 +63,41 @@ class _LoginEmpleadoScreenState extends State<LoginEmpleadoScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: LoginCard(
userType: 'Empleado',
isLoading: _isLoading,
onLogin: (correo, cedula) => _iniciarSesion(correo, cedula),
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/bg.png'),
fit: BoxFit.cover,
),
),
child: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
Row(
children: [
IconButton(
onPressed: () => Navigator.pop(context),
icon: const Icon(
Icons.arrow_back_ios,
color: Colors.white,
size: 24,
),
),
],
),
Container(
width: double.infinity,
child: LoginCard(
userType: 'Empleado',
isLoading: _isLoading,
onLogin: (correo, cedula) => _iniciarSesion(correo, cedula),
),
),
],
),
),
),
),
Expand Down
Loading
Loading