Agrego frontend app
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:gimnasio_soma/features/pagos/presentation/providers/pagos_provider.dart';
|
||||
import 'package:gimnasio_soma/features/tipos_cuota/presentation/providers/tipos_cuota_provider.dart';
|
||||
import 'package:gimnasio_soma/features/usuarios/data/repositories/usuarios_repository_impl.dart';
|
||||
import 'package:gimnasio_soma/features/usuarios/domain/entities/usuario.dart';
|
||||
import 'package:gimnasio_soma/features/usuarios/domain/repositories/usuarios_repository.dart';
|
||||
|
||||
final usuariosRepositoryProvider = Provider<UsuariosRepository>((ref) {
|
||||
return UsuariosRepositoryImpl();
|
||||
});
|
||||
|
||||
/// Todos los usuarios sin filtro. Se invalida automáticamente cuando usuariosProvider muta.
|
||||
final allUsuariosProvider = FutureProvider.autoDispose<List<Usuario>>((ref) async {
|
||||
final repo = ref.read(usuariosRepositoryProvider);
|
||||
return repo.getUsuarios();
|
||||
});
|
||||
|
||||
/// Mapa DNI → monto de deuda del mes actual.
|
||||
/// Se recalcula automáticamente cuando allUsuariosProvider o tiposCuotaProvider cambian.
|
||||
final usuariosDeudaProvider = FutureProvider.autoDispose<Map<String, double>>((ref) async {
|
||||
final tiposCuota = ref.watch(tiposCuotaProvider).valueOrNull;
|
||||
if (tiposCuota == null || tiposCuota.isEmpty) return {};
|
||||
|
||||
final usuarios = ref.watch(allUsuariosProvider).valueOrNull;
|
||||
if (usuarios == null || usuarios.isEmpty) return {};
|
||||
|
||||
final pagosRepo = ref.read(pagosRepositoryProvider);
|
||||
final pagos = await pagosRepo.getPagos(cantidad: 500);
|
||||
|
||||
final now = DateTime.now();
|
||||
final mesActual =
|
||||
'${now.year}-${now.month.toString().padLeft(2, '0')}-01';
|
||||
|
||||
final pagosMes = <String, double>{};
|
||||
for (final p in pagos) {
|
||||
if (p.anioMesPagado == mesActual && p.cliente != null) {
|
||||
pagosMes[p.cliente!.dni] =
|
||||
(pagosMes[p.cliente!.dni] ?? 0) + p.montoTotal;
|
||||
}
|
||||
}
|
||||
|
||||
final deuda = <String, double>{};
|
||||
for (final u in usuarios) {
|
||||
if (u.tipoCuota == null || !u.isActive) continue;
|
||||
|
||||
final tc =
|
||||
tiposCuota.where((t) => t.id == u.tipoCuota).firstOrNull;
|
||||
if (tc == null) continue;
|
||||
|
||||
double esperado = tc.precio;
|
||||
if (tc.recargo != null &&
|
||||
tc.recargo! > 0 &&
|
||||
now.day > tc.diaDePago) {
|
||||
esperado += tc.recargo!;
|
||||
}
|
||||
|
||||
final pagado = pagosMes[u.dni] ?? 0;
|
||||
deuda[u.dni] = esperado - pagado;
|
||||
}
|
||||
|
||||
return deuda;
|
||||
});
|
||||
|
||||
/// Helper compartido: retorna null si el usuario no tiene plan o está inactivo,
|
||||
/// 0.0 si está al día, >0 si debe.
|
||||
double? getUsuarioDeuda(Usuario u, Map<String, double> deudaMap) {
|
||||
if (u.tipoCuota == null || !u.isActive) return null;
|
||||
return deudaMap[u.dni] ?? 0.0;
|
||||
}
|
||||
|
||||
final usuariosProvider =
|
||||
StateNotifierProvider.autoDispose<UsuariosNotifier, AsyncValue<List<Usuario>>>((ref) {
|
||||
return UsuariosNotifier(ref.read(usuariosRepositoryProvider), ref);
|
||||
});
|
||||
|
||||
class UsuariosNotifier extends StateNotifier<AsyncValue<List<Usuario>>> {
|
||||
final UsuariosRepository _repository;
|
||||
final Ref _ref;
|
||||
|
||||
UsuariosNotifier(this._repository, this._ref) : super(const AsyncValue.loading()) {
|
||||
loadUsuarios();
|
||||
}
|
||||
|
||||
/// Invalida providers derivados para que refetcheen con datos frescos.
|
||||
void _invalidateDerived() {
|
||||
_ref.invalidate(allUsuariosProvider);
|
||||
// usuariosDeudaProvider y pagosEstadoProvider se recalculan solos
|
||||
// porque hacen ref.watch(allUsuariosProvider)
|
||||
}
|
||||
|
||||
Future<void> loadUsuarios() async {
|
||||
state = const AsyncValue.loading();
|
||||
try {
|
||||
final usuarios = await _repository.getUsuarios();
|
||||
state = AsyncValue.data(usuarios);
|
||||
_invalidateDerived();
|
||||
} catch (e, st) {
|
||||
state = AsyncValue.error(e, st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> insertUsuario(Map<String, dynamic> datos) async {
|
||||
try {
|
||||
await _repository.insertUsuario(datos);
|
||||
await loadUsuarios();
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e.toString().replaceFirst('Exception: ', '');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> updateUsuario(Map<String, dynamic> datos) async {
|
||||
try {
|
||||
await _repository.updateUsuario(datos);
|
||||
await loadUsuarios();
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e.toString().replaceFirst('Exception: ', '');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> toggleStatus(String id, bool estado) async {
|
||||
try {
|
||||
await _repository.toggleUsuarioStatus(id, estado);
|
||||
await loadUsuarios();
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e.toString().replaceFirst('Exception: ', '');
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> deleteUsuario(String dni) async {
|
||||
try {
|
||||
await _repository.deleteUsuario(dni);
|
||||
await loadUsuarios();
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e.toString().replaceFirst('Exception: ', '');
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset administrativo de contraseña. No refetchea la lista: no cambia
|
||||
/// ningún dato visible en ella.
|
||||
Future<String?> resetearContrasena(String usuarioId, String passwordNueva) async {
|
||||
try {
|
||||
await _repository.resetearContrasena(usuarioId, passwordNueva);
|
||||
return null;
|
||||
} catch (e) {
|
||||
return e.toString().replaceFirst('Exception: ', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:gimnasio_soma/core/config/app_constants.dart';
|
||||
|
||||
enum UsuariosViewMode { overview, cards, table }
|
||||
|
||||
final usuariosViewModeProvider =
|
||||
StateNotifierProvider<UsuariosViewModeNotifier, UsuariosViewMode>((ref) {
|
||||
return UsuariosViewModeNotifier();
|
||||
});
|
||||
|
||||
class UsuariosViewModeNotifier extends StateNotifier<UsuariosViewMode> {
|
||||
UsuariosViewModeNotifier() : super(UsuariosViewMode.overview) {
|
||||
_loadViewMode();
|
||||
}
|
||||
|
||||
Future<void> _loadViewMode() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final stored = prefs.getString(AppConstants.usuariosViewModeKey);
|
||||
if (stored == 'table') {
|
||||
state = UsuariosViewMode.table;
|
||||
} else if (stored == 'cards') {
|
||||
state = UsuariosViewMode.cards;
|
||||
} else {
|
||||
state = UsuariosViewMode.overview;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggle() async {
|
||||
final newMode = switch (state) {
|
||||
UsuariosViewMode.overview => UsuariosViewMode.cards,
|
||||
UsuariosViewMode.cards => UsuariosViewMode.table,
|
||||
UsuariosViewMode.table => UsuariosViewMode.overview,
|
||||
};
|
||||
await setMode(newMode);
|
||||
}
|
||||
|
||||
Future<void> setMode(UsuariosViewMode mode) async {
|
||||
state = mode;
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final stored = switch (mode) {
|
||||
UsuariosViewMode.table => 'table',
|
||||
UsuariosViewMode.cards => 'cards',
|
||||
UsuariosViewMode.overview => 'overview',
|
||||
};
|
||||
await prefs.setString(AppConstants.usuariosViewModeKey, stored);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user