fix: align route DTOs and field names with NestJS backend
- event_model: snake_case fields, status translation map (es→en), public-calendar route - chat_model: both roles use GET /chat/my (JWT identifies user) - score: correct POST /comments DTO (destination_id, service_id, content, score, is_from_user) - horario: PATCH /professionals/me/schedules with array format instead of /users/me Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b95e0630de
commit
ce11aa813e
@@ -38,28 +38,22 @@ class ChatModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<ChatModel>> getChatsByProId(String userId) async {
|
// Both roles use GET /chat/my — JWT identifies the current user
|
||||||
try {
|
static Future<List<ChatModel>> getChatsByProId(String userId) =>
|
||||||
final List<dynamic> data =
|
_getMyChats();
|
||||||
await ApiService.instance.get('/chat?professionalId=$userId');
|
|
||||||
return data
|
|
||||||
.map((e) => ChatModel.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
|
||||||
print('error getChatsByProId $e');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<List<ChatModel>> getChatsByUserId(String userId) async {
|
static Future<List<ChatModel>> getChatsByUserId(String userId) =>
|
||||||
|
_getMyChats();
|
||||||
|
|
||||||
|
static Future<List<ChatModel>> _getMyChats() async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> data =
|
final data = await ApiService.instance.get('/chat/my');
|
||||||
await ApiService.instance.get('/chat?userId=$userId');
|
final List raw = data is List ? data : (data['data'] ?? []);
|
||||||
return data
|
return raw
|
||||||
.map((e) => ChatModel.fromJson(e as Map<String, dynamic>))
|
.map((e) => ChatModel.fromJson(e as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('error getChatsByUserId $e');
|
print('error getMyChats $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+105
-60
@@ -4,6 +4,16 @@ import 'package:prosappco/src/services/api_service.dart';
|
|||||||
|
|
||||||
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
final uid = AuthenticationRepository.instance.getCurrentUserUid();
|
||||||
|
|
||||||
|
// Backend status strings
|
||||||
|
const _statusMap = {
|
||||||
|
'pendiente': 'pending',
|
||||||
|
'aprobado': 'accepted',
|
||||||
|
'negado': 'denied',
|
||||||
|
'activo': 'active',
|
||||||
|
'cancelado': 'cancelled',
|
||||||
|
'completado': 'completed',
|
||||||
|
};
|
||||||
|
|
||||||
class EventoService {
|
class EventoService {
|
||||||
Future<String?> createEvent(
|
Future<String?> createEvent(
|
||||||
String title,
|
String title,
|
||||||
@@ -22,22 +32,32 @@ class EventoService {
|
|||||||
bool userScored,
|
bool userScored,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic> result = await ApiService.instance.post('/services', {
|
String fmt(String t) {
|
||||||
'user_id': uid,
|
// Normalize "8:30 AM" / "08:30" → "HH:MM"
|
||||||
'title': title,
|
try {
|
||||||
'description': description,
|
final parts = t.replaceAll(RegExp(r'[APM ]'), '').split(':');
|
||||||
'day': day,
|
final h = int.parse(parts[0]).toString().padLeft(2, '0');
|
||||||
'range1Hour1': range1Hour1,
|
final m = (parts.length > 1 ? int.parse(parts[1]) : 0)
|
||||||
'range1Hour2': range1Hour2,
|
.toString()
|
||||||
|
.padLeft(2, '0');
|
||||||
|
return '$h:$m';
|
||||||
|
} catch (_) {
|
||||||
|
return '00:00';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, dynamic> result =
|
||||||
|
await ApiService.instance.post('/services', {
|
||||||
'professional_id': professionalId,
|
'professional_id': professionalId,
|
||||||
'ubicacion': ubicacion,
|
'day': day,
|
||||||
|
'description': description,
|
||||||
|
'rate': tarifa ?? 0,
|
||||||
|
'range1_hour1': fmt(range1Hour1),
|
||||||
|
'range1_hour2': fmt(range1Hour2),
|
||||||
'address': address,
|
'address': address,
|
||||||
'latitude': latitude,
|
'latitude': latitude,
|
||||||
'longitude': longitude,
|
'longitude': longitude,
|
||||||
'status': status,
|
'location_preference': ubicacion == 'domicilio' ? 'delivery' : 'office',
|
||||||
'tarifa': tarifa ?? 0,
|
|
||||||
'professional_scored': professionalScored,
|
|
||||||
'user_scored': userScored,
|
|
||||||
});
|
});
|
||||||
return result['id']?.toString();
|
return result['id']?.toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -47,54 +67,53 @@ class EventoService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calendar for logged-in professional — uses JWT, ignores day filter on backend
|
||||||
Future<List<Event>> getByProId(String day) async {
|
Future<List<Event>> getByProId(String day) async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> data = await ApiService.instance
|
final data = await ApiService.instance.get('/services/professional/calendar');
|
||||||
.get('/services?day=$day&status=aprobado');
|
final List raw = data is List ? data : (data['data'] ?? []);
|
||||||
List<Event> eventos = [];
|
return raw
|
||||||
for (var element in data) {
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
final event = Event.fromJson(element as Map<String, dynamic>);
|
.where((e) => e.day.startsWith(day.substring(0, 10)))
|
||||||
event.scoresModel =
|
.toList();
|
||||||
await ScoresModel.scoreTo(event.userId, false, false);
|
|
||||||
eventos.add(event);
|
|
||||||
}
|
|
||||||
return eventos;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByProId $e');
|
print('Error getByProId $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active/accepted services for logged-in professional
|
||||||
Future<List<Event>> getByProIdAll(String state1, String state2) async {
|
Future<List<Event>> getByProIdAll(String state1, String state2) async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> data = await ApiService.instance
|
final data = await ApiService.instance.get('/services/professional');
|
||||||
.get('/services?professionalId=$uid&status=$state1,$state2');
|
final List raw = data is Map ? (data['data'] ?? []) : (data as List);
|
||||||
List<Event> eventos = [];
|
final allowed = {
|
||||||
for (var element in data) {
|
_statusMap[state1] ?? state1,
|
||||||
final event = Event.fromJson(element as Map<String, dynamic>);
|
_statusMap[state2] ?? state2,
|
||||||
event.scoresModel =
|
};
|
||||||
await ScoresModel.scoreTo(event.userId, false, false);
|
return raw
|
||||||
eventos.add(event);
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
}
|
.where((e) => allowed.contains(e.status))
|
||||||
return eventos;
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByProIdAll $e');
|
print('Error getByProIdAll $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Active/accepted services for logged-in user
|
||||||
Future<List<Event>> getByUserIdAll(String state1, String state2) async {
|
Future<List<Event>> getByUserIdAll(String state1, String state2) async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> data = await ApiService.instance
|
final data = await ApiService.instance.get('/services/me');
|
||||||
.get('/services?userId=$uid&status=$state1,$state2');
|
final List raw = data is Map ? (data['data'] ?? []) : (data as List);
|
||||||
List<Event> eventos = [];
|
final allowed = {
|
||||||
for (var element in data) {
|
_statusMap[state1] ?? state1,
|
||||||
final event = Event.fromJson(element as Map<String, dynamic>);
|
_statusMap[state2] ?? state2,
|
||||||
event.scoresModel =
|
};
|
||||||
await ScoresModel.scoreTo(event.userId, false, false);
|
return raw
|
||||||
eventos.add(event);
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
}
|
.where((e) => allowed.contains(e.status))
|
||||||
return eventos;
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getByUserIdAll $e');
|
print('Error getByUserIdAll $e');
|
||||||
return [];
|
return [];
|
||||||
@@ -134,7 +153,7 @@ class Event {
|
|||||||
this.address,
|
this.address,
|
||||||
this.longitud,
|
this.longitud,
|
||||||
this.latitud,
|
this.latitud,
|
||||||
this.status = 'pendiente',
|
this.status = 'pending',
|
||||||
this.timeStamp,
|
this.timeStamp,
|
||||||
this.tarifa,
|
this.tarifa,
|
||||||
this.professionalScored = false,
|
this.professionalScored = false,
|
||||||
@@ -143,25 +162,44 @@ class Event {
|
|||||||
|
|
||||||
factory Event.fromJson(Map<String, dynamic> json) {
|
factory Event.fromJson(Map<String, dynamic> json) {
|
||||||
DateTime? ts;
|
DateTime? ts;
|
||||||
final raw = json['Timestamp'] ?? json['created_at'];
|
final raw = json['created_at'] ?? json['Timestamp'];
|
||||||
if (raw is String) ts = DateTime.tryParse(raw);
|
if (raw is String) ts = DateTime.tryParse(raw);
|
||||||
|
|
||||||
|
// Backend sends ISO date for range times; extract HH:MM
|
||||||
|
String parseTime(dynamic v) {
|
||||||
|
if (v == null) return '';
|
||||||
|
final s = v.toString();
|
||||||
|
if (s.contains('T')) {
|
||||||
|
final dt = DateTime.tryParse(s);
|
||||||
|
if (dt != null) {
|
||||||
|
return '${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested professional user_id takes priority as professionalId
|
||||||
|
final nestedPro = json['professionals'];
|
||||||
|
final professionalId = (nestedPro is Map)
|
||||||
|
? (nestedPro['user_id']?.toString() ?? json['professional_id']?.toString() ?? '')
|
||||||
|
: json['professional_id']?.toString() ?? '';
|
||||||
|
|
||||||
return Event(
|
return Event(
|
||||||
id: json['id']?.toString() ?? '',
|
id: json['id']?.toString() ?? '',
|
||||||
title: json['title'] ?? '',
|
title: json['description'] ?? json['title'] ?? '',
|
||||||
description: json['description'],
|
description: json['description'],
|
||||||
day: json['day'] ?? '',
|
day: json['day'] ?? '',
|
||||||
range1Hour1: json['range1Hour1'] ?? '',
|
range1Hour1: parseTime(json['range1_hour1'] ?? json['range1Hour1']),
|
||||||
range1Hour2: json['range1Hour2'],
|
range1Hour2: parseTime(json['range1_hour2'] ?? json['range1Hour2']),
|
||||||
userId: json['user_id'] ?? '',
|
userId: json['user_id'] ?? '',
|
||||||
professionalId: json['professional_id'] ?? '',
|
professionalId: professionalId,
|
||||||
ubicacion: json['ubicacion'] ?? '',
|
ubicacion: json['location_preference'] ?? json['ubicacion'] ?? '',
|
||||||
address: json['address'] ?? '',
|
address: json['address'] ?? '',
|
||||||
longitud: (json['longitude'] ?? 0).toDouble(),
|
longitud: (json['longitude'] ?? 0).toDouble(),
|
||||||
latitud: (json['latitude'] ?? 0).toDouble(),
|
latitud: (json['latitude'] ?? 0).toDouble(),
|
||||||
status: json['status'] ?? 'pendiente',
|
status: json['status'] ?? 'pending',
|
||||||
timeStamp: ts,
|
timeStamp: ts,
|
||||||
tarifa: json['tarifa'] ?? json['tarifas'] ?? 0,
|
tarifa: (json['rate'] as num?)?.toInt() ?? json['tarifa'] ?? 0,
|
||||||
professionalScored: json['professional_scored'] ?? false,
|
professionalScored: json['professional_scored'] ?? false,
|
||||||
userScored: json['user_scored'] ?? false,
|
userScored: json['user_scored'] ?? false,
|
||||||
);
|
);
|
||||||
@@ -179,11 +217,13 @@ class Event {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All services for a given professional (by user_id) — uses public calendar
|
||||||
static Future<List<Event>> getEventsAllById(String proId) async {
|
static Future<List<Event>> getEventsAllById(String proId) async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> data =
|
final data =
|
||||||
await ApiService.instance.get('/services?professionalId=$proId');
|
await ApiService.instance.get('/services/public-calendar/$proId');
|
||||||
return data
|
final List raw = (data is Map ? data['services'] : data) ?? [];
|
||||||
|
return raw
|
||||||
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -194,10 +234,12 @@ class Event {
|
|||||||
|
|
||||||
static Future<List<Event>> getEventsAllByIdAndStatus(String proId) async {
|
static Future<List<Event>> getEventsAllByIdAndStatus(String proId) async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> data = await ApiService.instance
|
final data =
|
||||||
.get('/services?professionalId=$proId&status=aprobado,pendiente');
|
await ApiService.instance.get('/services/public-calendar/$proId');
|
||||||
return data
|
final List raw = (data is Map ? data['services'] : data) ?? [];
|
||||||
|
return raw
|
||||||
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
|
.where((e) => e.status == 'accepted' || e.status == 'pending')
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getEventsAllByIdAndStatus $e');
|
print('Error getEventsAllByIdAndStatus $e');
|
||||||
@@ -208,10 +250,13 @@ class Event {
|
|||||||
static Future<List<Event>> getEventsAllByIdStatus(
|
static Future<List<Event>> getEventsAllByIdStatus(
|
||||||
String proId, String state) async {
|
String proId, String state) async {
|
||||||
try {
|
try {
|
||||||
final List<dynamic> data = await ApiService.instance
|
final backendStatus = _statusMap[state] ?? state;
|
||||||
.get('/services?professionalId=$proId&status=$state');
|
final data =
|
||||||
return data
|
await ApiService.instance.get('/services/professional/calendar');
|
||||||
|
final List raw = data is List ? data : [];
|
||||||
|
return raw
|
||||||
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
.map((e) => Event.fromJson(e as Map<String, dynamic>))
|
||||||
|
.where((e) => e.status == backendStatus)
|
||||||
.toList();
|
.toList();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error getEventsAllByIdStatus $e');
|
print('Error getEventsAllByIdStatus $e');
|
||||||
|
|||||||
@@ -44,12 +44,38 @@ class HorarioScreen extends StatelessWidget {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await ApiService.instance.patch('/users/me', {'horario': horariosMap});
|
// Convert map keyed by day number to array format expected by backend
|
||||||
|
final List<Map<String, dynamic>> schedulesList = [];
|
||||||
|
horariosMap.forEach((key, value) {
|
||||||
|
schedulesList.add({
|
||||||
|
'day_of_week': int.tryParse(key) ?? 1,
|
||||||
|
'enabled': value['habilitado'] ?? false,
|
||||||
|
'continuous_day': value['jornadaContinua'] ?? false,
|
||||||
|
'range1_hour1': _fmtTime(value['range1Hour1']),
|
||||||
|
'range1_hour2': _fmtTime(value['range1Hour2']),
|
||||||
|
'range2_hour1': _fmtTime(value['range2Hour1']),
|
||||||
|
'range2_hour2': _fmtTime(value['range2Hour2']),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
await ApiService.instance
|
||||||
|
.patch('/professionals/me/schedules', {'schedules': schedulesList});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Error al actualizar el horario: $e');
|
print('Error al actualizar el horario: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert "8:30 AM" / "HH:MM" strings to "HH:MM" for the backend
|
||||||
|
String? _fmtTime(String? t) {
|
||||||
|
if (t == null) return null;
|
||||||
|
try {
|
||||||
|
final clean = t.replaceAll(RegExp(r'\s?[APM]+', caseSensitive: false), '').trim();
|
||||||
|
final parts = clean.split(':');
|
||||||
|
return '${int.parse(parts[0]).toString().padLeft(2, '0')}:${(parts.length > 1 ? int.parse(parts[1]) : 0).toString().padLeft(2, '0')}';
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String? formatTimeOfDay(TimeOfDay? time) {
|
String? formatTimeOfDay(TimeOfDay? time) {
|
||||||
if (time != null) {
|
if (time != null) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
|
|||||||
@@ -51,18 +51,15 @@ class ScoreScreenState extends State<ScoreScreen> {
|
|||||||
Future<void> _submit() async {
|
Future<void> _submit() async {
|
||||||
try {
|
try {
|
||||||
final bool isPro = widget.pro;
|
final bool isPro = widget.pro;
|
||||||
// Update the service scored flag
|
// Backend sets professional_scored/user_scored automatically on POST /comments
|
||||||
await ApiService.instance.patch('/services/${widget.evento.id}', {
|
|
||||||
if (isPro) 'professional_scored': true,
|
|
||||||
if (!isPro) 'user_scored': true,
|
|
||||||
});
|
|
||||||
// Post the score/comment
|
|
||||||
await ApiService.instance.post('/comments', {
|
await ApiService.instance.post('/comments', {
|
||||||
'comment': commentController.text,
|
'service_id': widget.evento.id,
|
||||||
'from_user': isPro ? widget.evento.professionalId : widget.evento.userId,
|
'destination_id': isPro
|
||||||
'is_from_professional': !isPro,
|
? widget.evento.userId
|
||||||
|
: widget.evento.professionalId,
|
||||||
|
'content': commentController.text,
|
||||||
'score': _rating,
|
'score': _rating,
|
||||||
'to_user': isPro ? widget.evento.userId : widget.evento.professionalId,
|
'is_from_user': !isPro,
|
||||||
});
|
});
|
||||||
if (mounted) Navigator.pop(context);
|
if (mounted) Navigator.pop(context);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user