feat: add public employee profile view and implement ratings review page components

This commit is contained in:
2026-07-22 16:27:46 -03:00
parent 279e1b54cc
commit dd3e07311d
27 changed files with 1621 additions and 304 deletions
@@ -69,6 +69,7 @@ export class CompaniesAdapterMongoose implements ICompaniesAdapter {
banned: { type: Boolean, required: false, default: false },
showPublicScores: { type: Boolean, required: false, default: true },
showPublicOpinions: { type: Boolean, required: false, default: true },
showPublicProfessionals: { type: Boolean, required: false, default: true },
});
this.companyList = model<ICompanyDocument>("Company", this.schema);
@@ -245,6 +246,10 @@ export class CompaniesAdapterMongoose implements ICompaniesAdapter {
updateCompany.showPublicOpinions = data.showPublicOpinions;
}
if (data.showPublicProfessionals !== undefined) {
updateCompany.showPublicProfessionals = data.showPublicProfessionals;
}
updateCompany.save();
//actualizo los campos correspondientes a la localizacion de los servicios de la organizacion.
@@ -56,6 +56,7 @@ export type UpdateCompanyParams = {
banned?: boolean;
showPublicScores?: boolean;
showPublicOpinions?: boolean;
showPublicProfessionals?: boolean;
};
export type SetCompanyFileParams = {
@@ -158,6 +159,7 @@ export interface ICompany {
banned?: boolean;
showPublicScores?: boolean;
showPublicOpinions?: boolean;
showPublicProfessionals?: boolean;
}
export interface MyOranizationsView {
@@ -209,6 +211,7 @@ export interface MyOranizationsView {
banned?: boolean;
showPublicScores?: boolean;
showPublicOpinions?: boolean;
showPublicProfessionals?: boolean;
}
export interface ClientOrganizationView {
+8 -1
View File
@@ -500,7 +500,11 @@ class CompaniesManager implements ICompaniesManager {
throw new Error(NoPermissionMessage());
}
if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) {
if (
data.showPublicScores !== undefined ||
data.showPublicOpinions !== undefined ||
data.showPublicProfessionals !== undefined
) {
await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId));
}
@@ -614,6 +618,7 @@ class CompaniesManager implements ICompaniesManager {
onboardingCompleted: company.onboardingCompleted,
showPublicScores: company.showPublicScores ?? true,
showPublicOpinions: company.showPublicOpinions ?? true,
showPublicProfessionals: company.showPublicProfessionals ?? true,
};
}
@@ -735,6 +740,7 @@ class CompaniesManager implements ICompaniesManager {
onboardingCompleted: company.onboardingCompleted,
showPublicScores: company.showPublicScores ?? true,
showPublicOpinions: company.showPublicOpinions ?? true,
showPublicProfessionals: company.showPublicProfessionals ?? true,
});
}
@@ -807,6 +813,7 @@ class CompaniesManager implements ICompaniesManager {
onboardingCompleted: company.onboardingCompleted,
showPublicScores: company.showPublicScores ?? true,
showPublicOpinions: company.showPublicOpinions ?? true,
showPublicProfessionals: company.showPublicProfessionals ?? true,
});
}
}
+68
View File
@@ -8,6 +8,9 @@ import {
PaginateEmployeesParams,
PaginateEmployeesResults,
CompanyEmployeesView,
PublicCompanyEmployeeView,
FindPublicEmployeeParams,
PublicEmployeeView,
FindEmployeesParams,
FindEmployeesByIdParams,
UpdateEmployeeRolesParams,
@@ -442,6 +445,71 @@ class EmployeeManager implements IEmployeesManager {
return view;
}
public async findPublicByCompanyId(
data: FindEmployeesParams
): Promise<PublicCompanyEmployeeView[]> {
const employees = await this.employees.find({
companyId: data.companyId,
guestOk: true,
hostOk: true,
removed: { $ne: true },
} as any);
const view = await Promise.all(
employees.map(async (employee) => {
const userData = await UsersManager.users.findOne({ _id: employee.userId });
if (!userData) {
return null;
}
const strFirstName = isNull<string>(userData.firstName, "");
const strLastName = isNull<string>(userData.lastName, "");
const strFullName = joinStrings([strFirstName, strLastName], " ");
return {
employeeId: isNull<string>(employee.id, ""),
userId: employee.userId,
firstName: strFirstName,
lastName: strLastName,
avatarUrl: getAvatar(userData.id, userData.avatar, strFullName),
};
})
);
return view.filter((employee): employee is PublicCompanyEmployeeView => employee !== null);
}
public async findPublicByEmployeeId(data: FindPublicEmployeeParams): Promise<PublicEmployeeView> {
const employee = await this.employees.findOne({
_id: data.employeeId,
guestOk: true,
hostOk: true,
removed: { $ne: true },
} as any);
if (!employee) {
throw new Error("El colaborador no existe");
}
const userData = await UsersManager.users.findOne({ _id: employee.userId });
if (!userData) {
throw new Error("El colaborador no existe");
}
const strFirstName = isNull<string>(userData.firstName, "");
const strLastName = isNull<string>(userData.lastName, "");
const strFullName = joinStrings([strFirstName, strLastName], " ");
return {
employeeId: isNull<string>(employee.id, ""),
firstName: strFirstName,
lastName: strLastName,
avatarUrl: getAvatar(userData.id, userData.avatar, strFullName),
};
}
public async findById(data: FindEmployeesByIdParams): Promise<CompanyEmployeesView> {
const employee = await this.employees.findOne({ _id: data.id });
@@ -119,6 +119,25 @@ export interface CompanyEmployeesView {
};
}
export interface PublicCompanyEmployeeView {
employeeId: string;
userId: string;
firstName: string;
lastName: string;
avatarUrl: string;
}
export interface FindPublicEmployeeParams {
employeeId: string;
}
export interface PublicEmployeeView {
employeeId: string;
firstName: string;
lastName: string;
avatarUrl: string;
}
export interface IncompleteCollaboratorView {
employeeId: string;
companyId: string;
@@ -143,6 +162,8 @@ export interface IEmployeesManager {
updateEmployeeRoles(data: UpdateEmployeeRolesParams): Promise<void>;
update(data: UpdateEmployeeParams): Promise<void>;
findByCompanyId(data: FindEmployeesParams): Promise<CompanyEmployeesView[]>;
findPublicByCompanyId(data: FindEmployeesParams): Promise<PublicCompanyEmployeeView[]>;
findPublicByEmployeeId(data: FindPublicEmployeeParams): Promise<PublicEmployeeView>;
findById(data: FindEmployeesByIdParams): Promise<CompanyEmployeesView>;
textObjectFilter(data: FindEmployeesParams): Promise<TextObjectFilterResult[]>;
deleteEmployeesByCompany(data: DeleteEmployeesByCompany): Promise<void>;
@@ -8,8 +8,8 @@ import {
RATING_COMMENT_MAX_LENGTH,
RatingTargetSummaryParams,
RatingTargetSummaryResult,
RatingTargetReviewItem,
RatingTargetReviewsParams,
RatingTargetReviewsResult,
RatingTargetType,
} from "./Ratings.Interface";
@@ -127,20 +127,26 @@ export class RatingsAdapterMongoose implements IRatingsAdapter {
};
}
public async reviewsByTarget(filters: RatingTargetReviewsParams): Promise<RatingTargetReviewItem[]> {
public async reviewsByTarget(filters: RatingTargetReviewsParams): Promise<RatingTargetReviewsResult> {
const page = filters.page || 1;
const pageSize = filters.pageSize || filters.limit || 20;
const targetReviewsFilter = {
targetType: filters.targetType,
targetId: new Types.ObjectId(filters.targetId),
comment: { $regex: /\S/ },
};
const total = await this.ratingsList.countDocuments(targetReviewsFilter).exec();
const ratings = await this.ratingsList
.find({
targetType: filters.targetType,
targetId: new Types.ObjectId(filters.targetId),
comment: { $regex: /\S/ },
})
.find(targetReviewsFilter)
.sort({ score: -1, createdAt: -1 })
.limit(filters.limit || 20)
.skip((page - 1) * pageSize)
.limit(pageSize)
.select("score comment createdAt userId")
.populate("userId", "firstName lastName avatar")
.exec();
return (ratings as unknown as RatingReviewDocument[]).map((rating) => {
const reviews = (ratings as unknown as RatingReviewDocument[]).map((rating) => {
const user = rating.userId;
const displayName = user ? joinStrings([user.firstName, user.lastName], " ") : "";
const authorName = displayName || GENERIC_REVIEW_AUTHOR;
@@ -156,6 +162,14 @@ export class RatingsAdapterMongoose implements IRatingsAdapter {
},
};
});
return {
reviews,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
};
}
private mapRating(rating: IRatingDocument): IRating {
+12 -2
View File
@@ -53,6 +53,8 @@ export type RatingTargetSummaryResult = {
};
export type RatingTargetReviewsParams = RatingTargetSummaryParams & {
page?: number;
pageSize?: number;
limit?: number;
};
@@ -67,6 +69,14 @@ export type RatingTargetReviewItem = {
};
};
export type RatingTargetReviewsResult = {
reviews: RatingTargetReviewItem[];
total: number;
page: number;
pageSize: number;
totalPages: number;
};
export interface IRating {
id?: string;
appointmentId: string;
@@ -101,7 +111,7 @@ export interface IRatingsAdapter {
count(filters: FindRatingsParams): Promise<number>;
summaryByTarget(filters: RatingTargetSummaryParams): Promise<RatingTargetSummaryResult>;
summaryByTargets(filters: { targetType: RatingTargetType; targetIds: string[] }): Promise<RatingTargetSummaryResult>;
reviewsByTarget(filters: RatingTargetReviewsParams): Promise<RatingTargetReviewItem[]>;
reviewsByTarget(filters: RatingTargetReviewsParams): Promise<RatingTargetReviewsResult>;
}
export interface IRatingsManager {
@@ -111,5 +121,5 @@ export interface IRatingsManager {
countByUser(data: CountRatingsByUserParams): Promise<CountRatingsByUserResult>;
professionalSummaryByUser(data: ProfessionalRatingSummaryByUserParams): Promise<RatingTargetSummaryResult>;
summaryByTarget(data: RatingTargetSummaryParams): Promise<RatingTargetSummaryResult>;
reviewsByTarget(data: RatingTargetReviewsParams): Promise<RatingTargetReviewItem[]>;
reviewsByTarget(data: RatingTargetReviewsParams): Promise<RatingTargetReviewsResult>;
}
+8 -3
View File
@@ -20,8 +20,8 @@ import {
RATING_COMMENT_MAX_LENGTH,
RatingTargetSummaryParams,
RatingTargetSummaryResult,
RatingTargetReviewItem,
RatingTargetReviewsParams,
RatingTargetReviewsResult,
RatingTargetType,
} from "./Ratings.Interface";
@@ -221,16 +221,21 @@ class RatingsManager implements IRatingsManager {
return this.ratings.summaryByTarget(data);
}
public async reviewsByTarget(data: RatingTargetReviewsParams): Promise<RatingTargetReviewItem[]> {
public async reviewsByTarget(data: RatingTargetReviewsParams): Promise<RatingTargetReviewsResult> {
this.validateTargetType(data.targetType);
if (!Types.ObjectId.isValid(data.targetId)) {
throw new Error("No se ha proporcionado un objetivo válido.");
}
const pageSize = Math.min(Math.max(data.pageSize || data.limit || TARGET_REVIEWS_LIMIT, 1), TARGET_REVIEWS_LIMIT);
const page = Math.max(data.page || 1, 1);
return this.ratings.reviewsByTarget({
...data,
limit: Math.min(Math.max(data.limit || TARGET_REVIEWS_LIMIT, 1), TARGET_REVIEWS_LIMIT),
page,
pageSize,
limit: pageSize,
});
}
+2
View File
@@ -57,6 +57,7 @@ export type PublicOrganizationView = {
appointmentAlert: string;
showPublicScores: boolean;
showPublicOpinions: boolean;
showPublicProfessionals: boolean;
};
export type PublicOrganizationServiceViewParams = {
@@ -167,6 +168,7 @@ class Views {
appointmentAlert: isNull<string>(company.appointmentAlert, ""),
showPublicScores: company.showPublicScores ?? true,
showPublicOpinions: company.showPublicOpinions ?? true,
showPublicProfessionals: company.showPublicProfessionals ?? true,
};
}