Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
810 lines
29 KiB
Dart
810 lines
29 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:file_picker/file_picker.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:prosapp_web_app/models/pro_state.dart';
|
|
import 'package:prosapp_web_app/models/profesional.dart';
|
|
import 'package:prosapp_web_app/providers/auth_provider.dart';
|
|
import 'package:prosapp_web_app/providers/professional_form_provider.dart';
|
|
import 'package:prosapp_web_app/providers/professional_provider.dart';
|
|
import 'package:prosapp_web_app/providers/professions_provider.dart';
|
|
import 'package:prosapp_web_app/providers/profile_form_provider.dart';
|
|
import 'package:prosapp_web_app/providers/theme_provider.dart';
|
|
import 'package:prosapp_web_app/services/api_service.dart';
|
|
import 'package:prosapp_web_app/services/notifications_service.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
class RequestProfessionalView extends StatefulWidget {
|
|
const RequestProfessionalView({super.key});
|
|
|
|
@override
|
|
State<RequestProfessionalView> createState() =>
|
|
_RequestProfessionalViewState();
|
|
}
|
|
|
|
class _RequestProfessionalViewState extends State<RequestProfessionalView> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final auth = context.read<AuthProvider>();
|
|
context.read<ProfileFormProvider>().user = auth.user;
|
|
context
|
|
.read<ProfessionalProvider>()
|
|
.getProfessional(auth.user!.id)
|
|
.then((pro) =>
|
|
context.read<ProfessionalFormProvider>().setProfesional(pro))
|
|
.catchError((_) {
|
|
context.read<ProfessionalFormProvider>().setProfesional(Profesional.empty());
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListView(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
|
children: [
|
|
Center(
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 640),
|
|
child: const _FormBody(),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _FormBody extends StatefulWidget {
|
|
const _FormBody();
|
|
|
|
@override
|
|
State<_FormBody> createState() => _FormBodyState();
|
|
}
|
|
|
|
class _FormBodyState extends State<_FormBody> {
|
|
final _rethusCtrl = TextEditingController();
|
|
final _specCtrl = TextEditingController();
|
|
final _cedulaNumCtrl = TextEditingController();
|
|
String? _selectedProfession;
|
|
List<String> _specs = [];
|
|
bool _loading = false;
|
|
bool _cedulaError = false;
|
|
bool _cedulaNumError = false;
|
|
bool _certError = false;
|
|
bool _profError = false;
|
|
|
|
@override
|
|
void dispose() {
|
|
_rethusCtrl.dispose();
|
|
_specCtrl.dispose();
|
|
_cedulaNumCtrl.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _addSpec() {
|
|
final v = _specCtrl.text.trim();
|
|
if (v.isNotEmpty && !_specs.contains(v)) {
|
|
setState(() {
|
|
_specs.add(v);
|
|
_specCtrl.clear();
|
|
});
|
|
}
|
|
}
|
|
|
|
bool _validate(String cedulaUrl, String certUrl) {
|
|
final cErr = cedulaUrl.isEmpty;
|
|
final nErr = _cedulaNumCtrl.text.trim().isEmpty;
|
|
final dErr = certUrl.isEmpty;
|
|
final pErr = _selectedProfession == null || _selectedProfession!.isEmpty;
|
|
setState(() {
|
|
_cedulaError = cErr;
|
|
_cedulaNumError = nErr;
|
|
_certError = dErr;
|
|
_profError = pErr;
|
|
});
|
|
return !cErr && !nErr && !dErr && !pErr;
|
|
}
|
|
|
|
Future<void> _submit() async {
|
|
final fp = context.read<ProfessionalFormProvider>();
|
|
final cedulaUrl = fp.profesional?.identificationPicture ?? '';
|
|
final certUrl = fp.profesional?.certificatePicture ?? '';
|
|
if (!_validate(cedulaUrl, certUrl)) return;
|
|
|
|
setState(() => _loading = true);
|
|
try {
|
|
fp.copyProfesionalWith(
|
|
identification: _cedulaNumCtrl.text.trim(),
|
|
profession: _selectedProfession,
|
|
rethusCode: _rethusCtrl.text.trim(),
|
|
specializations: _specs,
|
|
);
|
|
|
|
await fp.submitForReview();
|
|
|
|
if (!mounted) return;
|
|
|
|
final pfp = context.read<ProfileFormProvider>();
|
|
pfp.copyUserWith(proState: ProState.pending);
|
|
await pfp.updateUserInfoNoValid();
|
|
await context.read<AuthProvider>().isAuthenticated();
|
|
} catch (e) {
|
|
if (mounted) {
|
|
NotificationsService.showSnackbar('Error al enviar: ${e.toString()}');
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => _loading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _pickAndUpload(
|
|
Future<dynamic> Function(Uint8List) upload,
|
|
) async {
|
|
final result = await FilePicker.platform.pickFiles(
|
|
type: FileType.custom,
|
|
allowedExtensions: ['pdf', 'jpg', 'jpeg', 'png'],
|
|
withData: true,
|
|
);
|
|
if (result == null || result.files.first.bytes == null) return;
|
|
if (!mounted) return;
|
|
NotificationsService.showBusyIndicator(context);
|
|
await upload(result.files.first.bytes!);
|
|
if (mounted) {
|
|
Navigator.pop(context);
|
|
setState(() => _cedulaError = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDark = context.watch<ThemeProvider>().isDark;
|
|
final fp = context.watch<ProfessionalFormProvider>();
|
|
final user = context.watch<AuthProvider>().user!;
|
|
final professions = context.watch<ProfessionsProvider>().professions;
|
|
final proStateInt = enumToInt(user.proState);
|
|
|
|
final cardBg = isDark ? const Color(0xFF1E293B) : Colors.white;
|
|
final border =
|
|
isDark ? const Color(0xFF334155) : const Color(0xFFE5E7EB);
|
|
final textPrimary = isDark ? Colors.white : const Color(0xFF111827);
|
|
final textSec =
|
|
isDark ? const Color(0xFF94A3B8) : const Color(0xFF6B7280);
|
|
|
|
if (fp.profesional == null) {
|
|
return const Padding(
|
|
padding: EdgeInsets.only(top: 60),
|
|
child: Center(child: CircularProgressIndicator()),
|
|
);
|
|
}
|
|
|
|
// En revisión
|
|
if (proStateInt == 1) {
|
|
return _StatusCard(
|
|
cardBg: cardBg,
|
|
border: border,
|
|
textPrimary: textPrimary,
|
|
textSec: textSec,
|
|
isPending: true,
|
|
);
|
|
}
|
|
|
|
// Rechazado
|
|
if (proStateInt == 3) {
|
|
return _StatusCard(
|
|
cardBg: cardBg,
|
|
border: border,
|
|
textPrimary: textPrimary,
|
|
textSec: textSec,
|
|
isPending: false,
|
|
);
|
|
}
|
|
|
|
final cedulaUploaded =
|
|
(fp.profesional?.identificationPicture ?? '').isNotEmpty;
|
|
final certUploaded =
|
|
(fp.profesional?.certificatePicture ?? '').isNotEmpty;
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// ── Header ──────────────────────────────────────────────────────────
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
gradient: const LinearGradient(
|
|
colors: [Color(0xFF42A4EF), Color(0xFF1565C0)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: const Column(
|
|
children: [
|
|
Icon(Icons.verified_user_outlined,
|
|
color: Colors.white, size: 34),
|
|
SizedBox(height: 8),
|
|
Text('Solicitud de Profesional',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 19,
|
|
fontWeight: FontWeight.bold)),
|
|
SizedBox(height: 4),
|
|
Text('Completa tu información para ofrecer servicios',
|
|
style: TextStyle(color: Colors.white70, fontSize: 12),
|
|
textAlign: TextAlign.center),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// ── 1. Cédula (número + foto) ───────────────────────────────────────
|
|
_SectionCard(
|
|
cardBg: cardBg,
|
|
border: border,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_Label('Cédula de ciudadanía', Icons.badge_outlined, required: true),
|
|
const SizedBox(height: 4),
|
|
// Tipo fijo
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
decoration: BoxDecoration(
|
|
color: isDark ? const Color(0xFF334155) : const Color(0xFFF1F5F9),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: border),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.lock_outline, size: 14, color: textSec),
|
|
const SizedBox(width: 6),
|
|
Text('Tipo: Cédula de ciudadanía', style: TextStyle(fontSize: 13, color: textSec)),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
// Número
|
|
TextField(
|
|
controller: _cedulaNumCtrl,
|
|
keyboardType: TextInputType.number,
|
|
style: TextStyle(color: textPrimary, fontSize: 14),
|
|
decoration: InputDecoration(
|
|
hintText: 'Número de cédula',
|
|
hintStyle: TextStyle(color: textSec, fontSize: 13),
|
|
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
|
errorText: _cedulaNumError ? 'Ingresa tu número de cédula' : null,
|
|
),
|
|
onChanged: (_) { if (_cedulaNumError) setState(() => _cedulaNumError = false); },
|
|
),
|
|
const SizedBox(height: 10),
|
|
// Foto
|
|
_UploadTile(
|
|
label: cedulaUploaded ? 'Foto de cédula subida ✓' : 'Subir foto o PDF de la cédula',
|
|
uploaded: cedulaUploaded,
|
|
onTap: () => _pickAndUpload((b) => fp.uploadPdfIdentification(b, user.id)),
|
|
),
|
|
if (_cedulaError)
|
|
const Padding(
|
|
padding: EdgeInsets.only(top: 8),
|
|
child: Text('Debes subir la foto de tu cédula',
|
|
style: TextStyle(color: Colors.redAccent, fontSize: 12)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// ── 2. Diploma / Certificado (obligatorio) ─────────────────────────
|
|
_SectionCard(
|
|
cardBg: cardBg,
|
|
border: border,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_Label('Diploma o certificado profesional', Icons.school_outlined, required: true),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Sube el diploma, acta de grado o tarjeta profesional',
|
|
style: TextStyle(fontSize: 11, color: textSec),
|
|
),
|
|
const SizedBox(height: 10),
|
|
_UploadTile(
|
|
label: certUploaded ? 'Documento subido ✓' : 'Subir diploma / certificado (PDF o imagen)',
|
|
uploaded: certUploaded,
|
|
onTap: () => _pickAndUpload((b) => fp.uploadPdfCertificate(b, user.id)),
|
|
),
|
|
if (_certError)
|
|
const Padding(
|
|
padding: EdgeInsets.only(top: 8),
|
|
child: Text('Debes subir el diploma o certificado',
|
|
style: TextStyle(color: Colors.redAccent, fontSize: 12)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// ── 4. Profesión (obligatorio) ──────────────────────────────────────
|
|
_SectionCard(
|
|
cardBg: cardBg,
|
|
border: border,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_Label('Profesión', Icons.work_outline, required: true),
|
|
const SizedBox(height: 12),
|
|
DropdownButtonFormField<String>(
|
|
value: _selectedProfession,
|
|
dropdownColor: cardBg,
|
|
style: TextStyle(color: textPrimary, fontSize: 14),
|
|
hint: Text('Selecciona tu profesión',
|
|
style: TextStyle(color: textSec, fontSize: 13)),
|
|
decoration: _dec(isDark, border),
|
|
items: professions
|
|
.map((p) => DropdownMenuItem(
|
|
value: p.name,
|
|
child: Text(p.name,
|
|
style: TextStyle(color: textPrimary)),
|
|
))
|
|
.toList(),
|
|
onChanged: (v) => setState(() {
|
|
_selectedProfession = v;
|
|
_profError = false;
|
|
}),
|
|
),
|
|
if (_profError)
|
|
const Padding(
|
|
padding: EdgeInsets.only(top: 8),
|
|
child: Text('Selecciona una profesión',
|
|
style: TextStyle(
|
|
color: Colors.redAccent, fontSize: 12)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// ── 3. RETHUS (opcional) ────────────────────────────────────────────
|
|
_SectionCard(
|
|
cardBg: cardBg,
|
|
border: border,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_Label('Código RETHUS',
|
|
Icons.health_and_safety_outlined,
|
|
required: false),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'Registro del talento humano en salud de Colombia',
|
|
style:
|
|
TextStyle(fontSize: 11, color: textSec)),
|
|
const SizedBox(height: 12),
|
|
TextField(
|
|
controller: _rethusCtrl,
|
|
style: TextStyle(color: textPrimary, fontSize: 14),
|
|
decoration: _dec(isDark, border)
|
|
.copyWith(hintText: 'Ej: 12345678'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 10),
|
|
|
|
// ── 4. Especializaciones (opcional) ────────────────────────────────
|
|
_SectionCard(
|
|
cardBg: cardBg,
|
|
border: border,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_Label('Especializaciones',
|
|
Icons.assignment_outlined,
|
|
required: false),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: TextField(
|
|
controller: _specCtrl,
|
|
style:
|
|
TextStyle(color: textPrimary, fontSize: 14),
|
|
onSubmitted: (_) => _addSpec(),
|
|
decoration: _dec(isDark, border).copyWith(
|
|
hintText: 'Ej: Fisioterapia deportiva'),
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Material(
|
|
color: const Color(0xFF42A4EF),
|
|
borderRadius: BorderRadius.circular(10),
|
|
child: InkWell(
|
|
onTap: _addSpec,
|
|
borderRadius: BorderRadius.circular(10),
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(13),
|
|
child: Icon(Icons.add,
|
|
color: Colors.white, size: 20),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (_specs.isNotEmpty) ...[
|
|
const SizedBox(height: 10),
|
|
Wrap(
|
|
spacing: 6,
|
|
runSpacing: 6,
|
|
children: _specs
|
|
.map((s) => Chip(
|
|
label: Text(s,
|
|
style: const TextStyle(
|
|
color: Color(0xFF42A4EF),
|
|
fontSize: 12)),
|
|
backgroundColor: const Color(0xFF42A4EF)
|
|
.withOpacity(0.1),
|
|
side: const BorderSide(
|
|
color: Color(0xFF42A4EF), width: 0.6),
|
|
deleteIconColor:
|
|
const Color(0xFF42A4EF),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius:
|
|
BorderRadius.circular(8)),
|
|
onDeleted: () =>
|
|
setState(() => _specs.remove(s)),
|
|
))
|
|
.toList(),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// ── Botón enviar ────────────────────────────────────────────────────
|
|
SizedBox(
|
|
height: 50,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF42A4EF),
|
|
foregroundColor: Colors.white,
|
|
disabledBackgroundColor:
|
|
const Color(0xFF42A4EF).withOpacity(0.6),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12)),
|
|
elevation: 0,
|
|
),
|
|
onPressed: _loading ? null : _submit,
|
|
child: _loading
|
|
? const SizedBox(
|
|
width: 22,
|
|
height: 22,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2, color: Colors.white),
|
|
)
|
|
: const Text('Enviar a revisión',
|
|
style: TextStyle(
|
|
fontSize: 15, fontWeight: FontWeight.w600)),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
],
|
|
);
|
|
}
|
|
|
|
InputDecoration _dec(bool isDark, Color border) {
|
|
final fill =
|
|
isDark ? const Color(0xFF0F172A) : const Color(0xFFF9FAFB);
|
|
final hint =
|
|
isDark ? const Color(0xFF64748B) : const Color(0xFF9CA3AF);
|
|
return InputDecoration(
|
|
hintStyle: TextStyle(color: hint, fontSize: 13),
|
|
filled: true,
|
|
fillColor: fill,
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide: BorderSide(color: border)),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide: BorderSide(color: border)),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide:
|
|
const BorderSide(color: Color(0xFF42A4EF), width: 2)),
|
|
contentPadding:
|
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
|
|
);
|
|
}
|
|
}
|
|
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
// Widgets de apoyo
|
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
|
|
class _SectionCard extends StatelessWidget {
|
|
final Color cardBg;
|
|
final Color border;
|
|
final Widget child;
|
|
const _SectionCard(
|
|
{required this.cardBg, required this.border, required this.child});
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: cardBg,
|
|
borderRadius: BorderRadius.circular(14),
|
|
border: Border.all(color: border),
|
|
),
|
|
child: child,
|
|
);
|
|
}
|
|
|
|
class _Label extends StatelessWidget {
|
|
final String text;
|
|
final IconData icon;
|
|
final bool required;
|
|
const _Label(this.text, this.icon, {required this.required});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDark = context.watch<ThemeProvider>().isDark;
|
|
final textPrimary = isDark ? Colors.white : const Color(0xFF111827);
|
|
return Row(
|
|
children: [
|
|
Icon(icon, size: 16, color: const Color(0xFF42A4EF)),
|
|
const SizedBox(width: 6),
|
|
Text(text,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w600,
|
|
color: textPrimary)),
|
|
if (required) ...[
|
|
const SizedBox(width: 3),
|
|
const Text('*',
|
|
style: TextStyle(
|
|
color: Colors.redAccent,
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.bold)),
|
|
] else
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 6),
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF64748B).withOpacity(0.12),
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: const Text('opcional',
|
|
style: TextStyle(
|
|
fontSize: 10, color: Color(0xFF64748B))),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _UploadTile extends StatelessWidget {
|
|
final String label;
|
|
final bool uploaded;
|
|
final VoidCallback onTap;
|
|
const _UploadTile(
|
|
{required this.label,
|
|
required this.uploaded,
|
|
required this.onTap});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color =
|
|
uploaded ? const Color(0xFF10B981) : const Color(0xFF42A4EF);
|
|
return InkWell(
|
|
onTap: onTap,
|
|
borderRadius: BorderRadius.circular(10),
|
|
child: Container(
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 14, vertical: 13),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.07),
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: color.withOpacity(0.35)),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
uploaded
|
|
? Icons.check_circle_outline
|
|
: Icons.upload_file_outlined,
|
|
color: color,
|
|
size: 20),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: Text(label,
|
|
style: TextStyle(
|
|
color: color,
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w500)),
|
|
),
|
|
Icon(
|
|
uploaded
|
|
? Icons.edit_outlined
|
|
: Icons.chevron_right,
|
|
color: color.withOpacity(0.6),
|
|
size: 18),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _StatusCard extends StatefulWidget {
|
|
final Color cardBg;
|
|
final Color border;
|
|
final Color textPrimary;
|
|
final Color textSec;
|
|
final bool isPending;
|
|
|
|
const _StatusCard({
|
|
required this.cardBg,
|
|
required this.border,
|
|
required this.textPrimary,
|
|
required this.textSec,
|
|
required this.isPending,
|
|
});
|
|
|
|
@override
|
|
State<_StatusCard> createState() => _StatusCardState();
|
|
}
|
|
|
|
class _StatusCardState extends State<_StatusCard> {
|
|
int _waitDays = 7;
|
|
bool _loadingSettings = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
if (!widget.isPending) _loadWaitDays();
|
|
}
|
|
|
|
Future<void> _loadWaitDays() async {
|
|
try {
|
|
final data = await ApiService.instance.get('/settings') as Map<String, dynamic>;
|
|
final v = data['rejection_wait_days'];
|
|
if (v != null) setState(() => _waitDays = (v as num).toInt());
|
|
} catch (_) {}
|
|
if (mounted) setState(() => _loadingSettings = false);
|
|
}
|
|
|
|
Future<void> _retry() async {
|
|
try {
|
|
await ApiService.instance.delete('/professionals/me');
|
|
} catch (e) {
|
|
if (mounted) NotificationsService.showSnackbar('Error al reiniciar solicitud: $e');
|
|
return;
|
|
}
|
|
if (!mounted) return;
|
|
await context.read<AuthProvider>().isAuthenticated();
|
|
if (!mounted) return;
|
|
// Reload professional data so the form shows fresh state
|
|
final auth = context.read<AuthProvider>();
|
|
context.read<ProfessionalProvider>()
|
|
.getProfessional(auth.user!.id)
|
|
.then((pro) => context.read<ProfessionalFormProvider>().setProfesional(pro))
|
|
.catchError((_) => context.read<ProfessionalFormProvider>().setProfesional(Profesional.empty()));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final accent = widget.isPending
|
|
? const Color(0xFFF59E0B)
|
|
: const Color(0xFFEF4444);
|
|
|
|
final user = context.watch<AuthProvider>().user;
|
|
final rejectedAt = user?.proRejectedAt;
|
|
final daysSince = rejectedAt != null
|
|
? DateTime.now().difference(rejectedAt).inDays
|
|
: _waitDays;
|
|
final daysLeft = (_waitDays - daysSince).clamp(0, _waitDays);
|
|
final canRetry = !widget.isPending && daysLeft == 0;
|
|
|
|
return Container(
|
|
margin: const EdgeInsets.only(top: 24),
|
|
padding: const EdgeInsets.all(32),
|
|
decoration: BoxDecoration(
|
|
color: widget.cardBg,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: widget.border),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
if (widget.isPending)
|
|
const Image(image: AssetImage('assets/checklist.gif'), width: 160)
|
|
else
|
|
Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: accent.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(Icons.sentiment_dissatisfied_outlined, size: 52, color: accent),
|
|
),
|
|
const SizedBox(height: 18),
|
|
Text(
|
|
widget.isPending ? 'Solicitud en revisión' : 'Solicitud no aprobada',
|
|
style: TextStyle(fontSize: 19, fontWeight: FontWeight.bold, color: widget.textPrimary),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Text(
|
|
widget.isPending
|
|
? 'Estamos revisando tus datos. Te notificaremos cuando tu cuenta esté aprobada.'
|
|
: canRetry
|
|
? 'Puedes volver a enviar tu solicitud con los documentos corregidos.'
|
|
: 'Tu solicitud no fue aprobada. Podrás reintentar en $daysLeft día${daysLeft == 1 ? '' : 's'}.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: widget.textSec, fontSize: 13, height: 1.5),
|
|
),
|
|
const SizedBox(height: 16),
|
|
if (!widget.isPending && !_loadingSettings) ...[
|
|
if (canRetry)
|
|
FilledButton.icon(
|
|
onPressed: _retry,
|
|
icon: const Icon(Icons.refresh, size: 18),
|
|
label: const Text('Volver a solicitar'),
|
|
style: FilledButton.styleFrom(backgroundColor: const Color(0xFF42A4EF)),
|
|
)
|
|
else
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: accent.withOpacity(0.12),
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: accent.withOpacity(0.4)),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.timer_outlined, color: accent, size: 16),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
'Disponible en $daysLeft día${daysLeft == 1 ? '' : 's'}',
|
|
style: TextStyle(color: accent, fontWeight: FontWeight.w600, fontSize: 13),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
] else if (widget.isPending)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: accent.withOpacity(0.12),
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: accent.withOpacity(0.4)),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.hourglass_empty_rounded, color: accent, size: 16),
|
|
const SizedBox(width: 6),
|
|
Text('Revisión en proceso',
|
|
style: TextStyle(color: accent, fontWeight: FontWeight.w600, fontSize: 13)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|