feat: implement full-stack ratings system for appointments with user submission and pending ratings dashboard integration

This commit is contained in:
2026-07-21 23:49:16 -03:00
parent b26c3e58d1
commit 383a5bc6b4
17 changed files with 1505 additions and 20 deletions
+22 -18
View File
@@ -3,6 +3,26 @@ import { ApiError } from "@models/Server.Error.model";
import axios from "@config/axios.config";
class ApiService {
private normalizeError(error: unknown): ApiError {
if (error && typeof error === "object" && "response" in error) {
const response = (error as { response?: { data?: any; status?: number } }).response;
const data = response?.data;
if (data) {
const msg = data.desc || data.message || JSON.stringify(data.details || data) || "No pudimos completar la operación.";
return new ApiError(data.code || response?.status || 500, msg);
}
return new ApiError(response?.status || 500, "No pudimos comunicarnos con el servidor.");
}
if (error instanceof Error) {
return new ApiError(500, error.message);
}
return new ApiError(500, "No pudimos comunicarnos con el servidor.");
}
/**
* Sends a HTTP POST request to the specified endpoint with the provided data
* and returns a promise that resolves to the response data.
@@ -19,14 +39,7 @@ class ApiService {
resolve(response.data);
})
.catch((error) => {
try {
const data = error.response.data;
const msg = data.desc || data.message || JSON.stringify(data.details || data);
reject(new ApiError(data.code || error.response.status || 500, msg));
} catch {
const errorMessage = process.env.VITE_API_UNAVAILABLE as string;
reject(new ApiError(500, errorMessage));
}
reject(this.normalizeError(error));
});
});
}
@@ -44,16 +57,7 @@ class ApiService {
resolve(response.data);
})
.catch((error) => {
try {
console.error("Full API Error:", error.response?.data);
const data = error.response.data;
const msg = data.desc || data.message || JSON.stringify(data.details || data);
reject(new ApiError(data.code || error.response.status || 500, msg));
} catch (e) {
console.error(e);
const errorMessage = process.env.VITE_API_UNAVAILABLE as string;
reject(new ApiError(500, errorMessage));
}
reject(this.normalizeError(error));
});
});
}
+52
View File
@@ -0,0 +1,52 @@
import ApiRequest from "@services/Api.Service";
import { ApiVoidResult } from "@core/Models/Api.VoidResult.type";
import { ApiError } from "@core/Models/Server.Error.model";
import {
CreateRatingParams,
PendingRatingItem,
PendingRatingsByUserParams,
RATING_COMMENT_MAX_LENGTH,
RatingTargetType,
} from "@core/Models/Ratings.model";
import * as Yup from "yup";
const schemaPendingRatingsByUser = Yup.object().shape({
sessionUser: Yup.string().required("Para ver tus calificaciones pendientes debes iniciar sesión."),
});
const schemaCreateRating = Yup.object().shape({
appointmentId: Yup.string().required("No se ha proporcionado el turno."),
targetType: Yup.string()
.oneOf(Object.values(RatingTargetType), "El tipo de calificación no es válido.")
.required("No se ha proporcionado el tipo de calificación."),
targetId: Yup.string().required("No se ha proporcionado el item a calificar."),
score: Yup.number().integer().min(1).max(5).required("Selecciona una calificación."),
comment: Yup.string().max(RATING_COMMENT_MAX_LENGTH, `El comentario no puede superar los ${RATING_COMMENT_MAX_LENGTH} caracteres.`),
sessionUser: Yup.string().required("Para calificar debes iniciar sesión."),
});
export const pendingRatingsByUser = async (data: PendingRatingsByUserParams): Promise<PendingRatingItem[]> => {
return new Promise<PendingRatingItem[]>((resolve, reject) => {
schemaPendingRatingsByUser
.validate(data, { abortEarly: true })
.then(() => {
resolve(ApiRequest.post<PendingRatingItem[]>("ratings/pending-by-user", data));
})
.catch((error) => {
reject(new ApiError(500, error.message));
});
});
};
export const createRating = async (data: CreateRatingParams): Promise<ApiVoidResult> => {
return new Promise<ApiVoidResult>((resolve, reject) => {
schemaCreateRating
.validate(data, { abortEarly: true })
.then(() => {
resolve(ApiRequest.post<ApiVoidResult>("ratings/create", data));
})
.catch((error) => {
reject(new ApiError(500, error.message));
});
});
};