diff --git a/server/src/Models/Companies/Companies.Adapter.Mongoose.ts b/server/src/Models/Companies/Companies.Adapter.Mongoose.ts index 04f0c11..72278ac 100644 --- a/server/src/Models/Companies/Companies.Adapter.Mongoose.ts +++ b/server/src/Models/Companies/Companies.Adapter.Mongoose.ts @@ -67,6 +67,8 @@ export class CompaniesAdapterMongoose implements ICompaniesAdapter { fixedPostIds: { type: Array, required: false }, banned: { type: Boolean, required: false, default: false }, + showPublicScores: { type: Boolean, required: false, default: true }, + showPublicOpinions: { type: Boolean, required: false, default: true }, }); this.companyList = model("Company", this.schema); @@ -235,6 +237,14 @@ export class CompaniesAdapterMongoose implements ICompaniesAdapter { updateCompany.banned = data.banned; } + if (data.showPublicScores !== undefined) { + updateCompany.showPublicScores = data.showPublicScores; + } + + if (data.showPublicOpinions !== undefined) { + updateCompany.showPublicOpinions = data.showPublicOpinions; + } + updateCompany.save(); //actualizo los campos correspondientes a la localizacion de los servicios de la organizacion. diff --git a/server/src/Models/Companies/Companies.Interface.ts b/server/src/Models/Companies/Companies.Interface.ts index a92ecd6..e6beeae 100644 --- a/server/src/Models/Companies/Companies.Interface.ts +++ b/server/src/Models/Companies/Companies.Interface.ts @@ -54,6 +54,8 @@ export type UpdateCompanyParams = { onboardingStep?: number; onboardingCompleted?: boolean; banned?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; }; export type SetCompanyFileParams = { @@ -154,6 +156,8 @@ export interface ICompany { onboardingCompleted?: boolean; fixedPostIds?: Array; banned?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; } export interface MyOranizationsView { @@ -203,6 +207,8 @@ export interface MyOranizationsView { onboardingStep?: number; onboardingCompleted?: boolean; banned?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; } export interface ClientOrganizationView { diff --git a/server/src/Models/Companies/Companies.ts b/server/src/Models/Companies/Companies.ts index 9aa1671..b1e4c3b 100644 --- a/server/src/Models/Companies/Companies.ts +++ b/server/src/Models/Companies/Companies.ts @@ -67,6 +67,17 @@ class CompaniesManager implements ICompaniesManager { this.companies = new CompaniesAdapterMongoose(); } + private async validatePublicRatingFlagsPlan(ownerId: string): Promise { + const { default: PlanSubscriptionsList } = await import("../PlanSubscriptions/PlanSubscriptons"); + const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ + sessionUser: ownerId, + }); + + if (!subscription || subscription.plan.price === 0) { + throw new Error("Esta configuración está disponible solo para planes pagos."); + } + } + public async toggleFixedPost(data: FixCompanyPostParams): Promise { await validateSessionUser({ sessionUser: data.sessionUser, @@ -489,6 +500,10 @@ class CompaniesManager implements ICompaniesManager { throw new Error(NoPermissionMessage()); } + if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) { + await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId)); + } + let updateData = { ...data, }; @@ -597,6 +612,8 @@ class CompaniesManager implements ICompaniesManager { templateEmailAltaId: isNull(company.templateEmailAltaId, ""), onboardingStep: company.onboardingStep, onboardingCompleted: company.onboardingCompleted, + showPublicScores: company.showPublicScores ?? true, + showPublicOpinions: company.showPublicOpinions ?? true, }; } @@ -716,6 +733,8 @@ class CompaniesManager implements ICompaniesManager { templateEmailAltaId: isNull(company.templateEmailAltaId, ""), onboardingStep: company.onboardingStep, onboardingCompleted: company.onboardingCompleted, + showPublicScores: company.showPublicScores ?? true, + showPublicOpinions: company.showPublicOpinions ?? true, }); } @@ -786,6 +805,8 @@ class CompaniesManager implements ICompaniesManager { templateEmailAltaId: isNull(company.templateEmailAltaId, ""), onboardingStep: company.onboardingStep, onboardingCompleted: company.onboardingCompleted, + showPublicScores: company.showPublicScores ?? true, + showPublicOpinions: company.showPublicOpinions ?? true, }); } } diff --git a/server/src/Models/Ratings/Ratings.Adapter.Mongoose.ts b/server/src/Models/Ratings/Ratings.Adapter.Mongoose.ts index a8ed983..e8b81f8 100644 --- a/server/src/Models/Ratings/Ratings.Adapter.Mongoose.ts +++ b/server/src/Models/Ratings/Ratings.Adapter.Mongoose.ts @@ -134,7 +134,7 @@ export class RatingsAdapterMongoose implements IRatingsAdapter { targetId: new Types.ObjectId(filters.targetId), comment: { $regex: /\S/ }, }) - .sort({ createdAt: -1 }) + .sort({ score: -1, createdAt: -1 }) .limit(filters.limit || 20) .select("score comment createdAt userId") .populate("userId", "firstName lastName avatar") diff --git a/server/src/Models/Services/Service.Adapter.Mongoose.ts b/server/src/Models/Services/Service.Adapter.Mongoose.ts index db75c1b..dd346b1 100644 --- a/server/src/Models/Services/Service.Adapter.Mongoose.ts +++ b/server/src/Models/Services/Service.Adapter.Mongoose.ts @@ -42,6 +42,8 @@ export class ServicesAdapterMongoose implements IServicesAdapter { longitude: { type: Number, required: false, default: 0 }, published: { type: String, required: false }, banned: { type: Boolean, required: false, default: false }, + showPublicScores: { type: Boolean, required: false, default: true }, + showPublicOpinions: { type: Boolean, required: false, default: true }, }); this.serviceList = model("Service", this.schema); @@ -93,6 +95,8 @@ export class ServicesAdapterMongoose implements IServicesAdapter { if (data.latitude) updateService.latitude = data.latitude; if (data.longitude) updateService.longitude = data.longitude; if (data.banned !== undefined) updateService.banned = data.banned; + if (data.showPublicScores !== undefined) updateService.showPublicScores = data.showPublicScores; + if (data.showPublicOpinions !== undefined) updateService.showPublicOpinions = data.showPublicOpinions; await updateService.save(); } diff --git a/server/src/Models/Services/Service.Interface.ts b/server/src/Models/Services/Service.Interface.ts index 53660bc..75c175d 100644 --- a/server/src/Models/Services/Service.Interface.ts +++ b/server/src/Models/Services/Service.Interface.ts @@ -57,6 +57,8 @@ export type CreateServiceParams = { latitude?: number; longitude?: number; sessionUser: string; + showPublicScores?: boolean; + showPublicOpinions?: boolean; }; export type UpdateServiceParams = { @@ -81,6 +83,8 @@ export type UpdateServiceParams = { longitude?: number; sessionUser: string; banned?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; }; export type PaginatePublicServicesParams = FindServicesParams & { @@ -155,6 +159,8 @@ export interface IService { published?: SERVICE_PUBLISHED_STATUS; discountId?: string; banned?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; } export interface CompanyServiceView { @@ -176,6 +182,8 @@ export interface CompanyServiceView { fontShadowColor: string; published: SERVICE_PUBLISHED_STATUS; banned?: boolean; + showPublicScores: boolean; + showPublicOpinions: boolean; } export interface PublicServiceView { @@ -199,6 +207,8 @@ export interface PublicServiceView { fontColor: string; fontShadowColor: string; banned?: boolean; + showPublicScores: boolean; + showPublicOpinions: boolean; } export interface FindServicesByCompanyParams { diff --git a/server/src/Models/Services/Service.ts b/server/src/Models/Services/Service.ts index d12a03e..1e2b2b6 100644 --- a/server/src/Models/Services/Service.ts +++ b/server/src/Models/Services/Service.ts @@ -46,6 +46,17 @@ class ServiceManager implements IServicesManager { this.services = new ServicesAdapterMongoose(); } + private async validatePublicRatingFlagsPlan(ownerId: string): Promise { + const { default: PlanSubscriptionsList } = await import("../PlanSubscriptions/PlanSubscriptons"); + const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ + sessionUser: ownerId, + }); + + if (!subscription || subscription.plan.price === 0) { + throw new Error("Esta configuración está disponible solo para planes pagos."); + } + } + public async setPublishedStatus(data: SetPublishedStatusParams): Promise { const sessionUser = await UsersManager.users.findOne({ _id: data.sessionUser, @@ -145,6 +156,10 @@ class ServiceManager implements IServicesManager { throw new Error("Ha alcanzado el limite de servicios permitidos de acuerdo a su plan."); } + if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) { + await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId)); + } + const newService = await this.services.create({ ...data, ...{ @@ -190,6 +205,10 @@ class ServiceManager implements IServicesManager { throw new Error(NoPermissionMessage()); } + if (data.showPublicScores !== undefined || data.showPublicOpinions !== undefined) { + await this.validatePublicRatingFlagsPlan(String(companyCheck.ownerId)); + } + await this.services.update(data); } @@ -307,6 +326,8 @@ class ServiceManager implements IServicesManager { service.published, SERVICE_PUBLISHED_STATUS.PRIVATE ), + showPublicScores: service.showPublicScores ?? true, + showPublicOpinions: service.showPublicOpinions ?? true, }); } @@ -363,6 +384,8 @@ class ServiceManager implements IServicesManager { fontColor: isNull(service.fontColor, defFontColor), fontShadowColor: isNull(service.fontShadowColor, defFontShadowColor), published: isNull(service.published, SERVICE_PUBLISHED_STATUS.PRIVATE), + showPublicScores: service.showPublicScores ?? true, + showPublicOpinions: service.showPublicOpinions ?? true, }; } @@ -486,6 +509,8 @@ class ServiceManager implements IServicesManager { color: isNull(service.color, defColor), fontColor: isNull(service.fontColor, defFontColor), fontShadowColor: isNull(service.fontShadowColor, defFontShadowColor), + showPublicScores: service.showPublicScores ?? true, + showPublicOpinions: service.showPublicOpinions ?? true, }); } diff --git a/server/src/Models/Views/Views.ts b/server/src/Models/Views/Views.ts index 4235c83..b9451e9 100644 --- a/server/src/Models/Views/Views.ts +++ b/server/src/Models/Views/Views.ts @@ -55,6 +55,8 @@ export type PublicOrganizationView = { latitude: number; longitude: number; appointmentAlert: string; + showPublicScores: boolean; + showPublicOpinions: boolean; }; export type PublicOrganizationServiceViewParams = { @@ -163,6 +165,8 @@ class Views { services, employees: services, appointmentAlert: isNull(company.appointmentAlert, ""), + showPublicScores: company.showPublicScores ?? true, + showPublicOpinions: company.showPublicOpinions ?? true, }; } diff --git a/txclient/src/Models/Company.model.ts b/txclient/src/Models/Company.model.ts index cf9b34d..6f626a4 100644 --- a/txclient/src/Models/Company.model.ts +++ b/txclient/src/Models/Company.model.ts @@ -45,6 +45,8 @@ export interface MyOranizationsView { templateEmailCancellationId: string; onboardingStep?: number; onboardingCompleted?: boolean; + showPublicScores: boolean; + showPublicOpinions: boolean; } export type CreateCompanyParams = { @@ -93,6 +95,8 @@ export type UpdateCompanyParams = { templateEmailCancellationId?: string; onboardingStep?: number; onboardingCompleted?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; }; export type DeleteCompanyParams = { @@ -158,6 +162,8 @@ export interface ICompany { onboardingCompleted?: boolean; fixedPostIds?: Array; banned?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; } export type PublicOrganizationViewParams = { @@ -184,6 +190,8 @@ export type PublicOrganizationView = { longitude: number; appointmentAlert: string; banned?: boolean; + showPublicScores: boolean; + showPublicOpinions: boolean; }; export type SetNotificationAutoParams = { diff --git a/txclient/src/Models/Service.model.ts b/txclient/src/Models/Service.model.ts index ac2b79d..2e3b5d9 100644 --- a/txclient/src/Models/Service.model.ts +++ b/txclient/src/Models/Service.model.ts @@ -20,6 +20,8 @@ export type CreateServiceParams = { originalPrice: number; limit: number; sessionUser: string; + showPublicScores?: boolean; + showPublicOpinions?: boolean; }; export type UpdateServiceParams = { @@ -36,6 +38,8 @@ export type UpdateServiceParams = { fontColor?: string; fontShadowColor?: string; sessionUser: string; + showPublicScores?: boolean; + showPublicOpinions?: boolean; }; export type PaginateServicesParams = { @@ -110,6 +114,8 @@ export interface PublicServiceView { fontColor: string; fontShadowColor: string; banned?: boolean; + showPublicScores: boolean; + showPublicOpinions: boolean; } export interface PublicOrganizationServiceView { @@ -132,6 +138,8 @@ export interface IService { fontColor: string; fontShadowColor: string; banned?: boolean; + showPublicScores?: boolean; + showPublicOpinions?: boolean; } export interface CompanyServiceView { @@ -153,4 +161,6 @@ export interface CompanyServiceView { fontShadowColor: string; published: SERVICE_PUBLISHED_STATUS; banned?: boolean; + showPublicScores: boolean; + showPublicOpinions: boolean; } diff --git a/txclient/src/app/admin/(organization-profile)/org/profile/[id]/edit/page.tsx b/txclient/src/app/admin/(organization-profile)/org/profile/[id]/edit/page.tsx index 770da1f..51e480e 100644 --- a/txclient/src/app/admin/(organization-profile)/org/profile/[id]/edit/page.tsx +++ b/txclient/src/app/admin/(organization-profile)/org/profile/[id]/edit/page.tsx @@ -11,12 +11,14 @@ import OrgConfigIcon from "@mui/icons-material/SettingsOutlined"; import Textbox from "@components/Textbox/Textbox"; import TextPhone from "@components/TextPhone/TextPhone"; import MaterialAlert from "@mui/material/Alert"; +import Checkbox from "@mui/material/Checkbox"; import * as Yup from "yup"; import { loadOrganizationsById, savePublishedStatus, updateOrganizationsById, } from "../Org.Service"; +import { loadServicesByCompany, updateService } from "../services/Services.Service"; import { COMPANY_PUBLISHED_STATUS, MyOranizationsView, @@ -35,6 +37,7 @@ import { useConfirmStore } from "@core/Store/Confirm.Store"; import Switch from "@core/app/components/Switch/Switch"; import { FindTemplatesParams } from "@core/Models/Templates.model"; import filterObjects from "@core/app/components/TextObjectFilter/Filter.Service"; +import { CompanyServiceView } from "@models/Service.model"; type UrlData = { id: string; @@ -57,6 +60,13 @@ export default function OrganizationEdit() { const [cancellationTime, setCancellationTime] = useState(""); const [appointmentAlert, setAppointmentAlert] = useState(""); const [orgSlug, setOrgSlug] = useState(""); + const [showPublicScores, setShowPublicScores] = useState(true); + const [showPublicOpinions, setShowPublicOpinions] = useState(true); + const [servicesShowPublicScores, setServicesShowPublicScores] = useState(true); + const [servicesShowPublicOpinions, setServicesShowPublicOpinions] = useState(true); + const [originalServicesShowPublicScores, setOriginalServicesShowPublicScores] = useState(true); + const [originalServicesShowPublicOpinions, setOriginalServicesShowPublicOpinions] = useState(true); + const [services, setServices] = useState([]); const [orgPublished, setOrgPublished] = useState( undefined @@ -65,6 +75,8 @@ export default function OrganizationEdit() { const [savePublishedStatusAvailable, setSavePublishedStatusAvailable] = useState(false); const [combosLoaded, setCombosLoaded] = useState(false); + const ownerSubscription = SessionInfo.organizationSubscriptions[id]; + const canEditPublicRatingFlags = (ownerSubscription?.plan.price || 0) > 0; const [categoryFilterContent] = useState({ header: ( @@ -210,28 +222,48 @@ export default function OrganizationEdit() { useEffect(() => { if (combosLoaded) { eventHandler.setEventType(EVENT_TYPES.LOADING); - loadOrganizationsById({ id: id, sessionUser: SessionInfo.userId }) - .then((response: MyOranizationsView) => { - setOrgName(response.name); - setOrgDescription(response.description); - setHeatMapFraction(response.heatMapFraction.toString()); - setAppointmentTime(response.appointmentTime.toString()); - setCancellationTime(response.cancellationTime.toString()); - setAppointmentAlert(response.appointmentAlert); - setAreaCode(response.phoneAreaCode); - setPhone(response.phoneNumber); - setCategoryId(response.categoryId.toString()); - setOrgSlug(getSlug(response.name)); - setOrgPublished(response.published); + Promise.all([ + loadOrganizationsById({ id: id, sessionUser: SessionInfo.userId }), + loadServicesByCompany(id), + ]) + .then((response) => { + const [organization, loadedServices] = response; + + setOrgName(organization.name); + setOrgDescription(organization.description); + setHeatMapFraction(organization.heatMapFraction.toString()); + setAppointmentTime(organization.appointmentTime.toString()); + setCancellationTime(organization.cancellationTime.toString()); + setAppointmentAlert(organization.appointmentAlert); + setAreaCode(organization.phoneAreaCode); + setPhone(organization.phoneNumber); + setCategoryId(organization.categoryId.toString()); + setOrgSlug(getSlug(organization.name)); + setOrgPublished(organization.published); + setShowPublicScores(organization.showPublicScores !== undefined ? organization.showPublicScores : true); + setShowPublicOpinions(organization.showPublicOpinions !== undefined ? organization.showPublicOpinions : true); + setServices(loadedServices); + setServicesShowPublicScores( + loadedServices.every((service) => service.showPublicScores !== false) + ); + setServicesShowPublicOpinions( + loadedServices.every((service) => service.showPublicOpinions !== false) + ); + setOriginalServicesShowPublicScores( + loadedServices.every((service) => service.showPublicScores !== false) + ); + setOriginalServicesShowPublicOpinions( + loadedServices.every((service) => service.showPublicOpinions !== false) + ); requestAnimationFrame(() => { - setTemplateWapNotifId(response.templateWapNotifId); - setTemplateWapAltaId(response.templateWapAltaId); - setTemplateWapCancellationId(response.templateWapCancellationId); + setTemplateWapNotifId(organization.templateWapNotifId); + setTemplateWapAltaId(organization.templateWapAltaId); + setTemplateWapCancellationId(organization.templateWapCancellationId); }); }) - .catch((error) => { - alert.showError(error.format()); + .catch((error: ApiError | Error) => { + alert.showError(error instanceof ApiError ? error.format() : error.message); }) .finally(() => { eventHandler.setEventType(EVENT_TYPES.SLEEP); @@ -270,6 +302,12 @@ export default function OrganizationEdit() { templateWapNotifId: templateWapNotifId === "" ? undefined : templateWapNotifId, templateWapAltaId: templateWapAltaId === "" ? undefined : templateWapAltaId, templateWapCancellationId: templateWapCancellationId === "" ? undefined : templateWapCancellationId, + ...(canEditPublicRatingFlags + ? { + showPublicScores, + showPublicOpinions, + } + : {}), }; const schema = Yup.object().shape({ @@ -335,10 +373,61 @@ export default function OrganizationEdit() { .then(() => { updateOrganizationsById(data) .then(() => { - alert.showSuccess("Organizacion actualizada!"); + const servicesShowPublicScoresChanged = + originalServicesShowPublicScores !== servicesShowPublicScores; + const servicesShowPublicOpinionsChanged = + originalServicesShowPublicOpinions !== servicesShowPublicOpinions; + const changedServices = + canEditPublicRatingFlags && + (servicesShowPublicScoresChanged || servicesShowPublicOpinionsChanged) + ? services + : []; + + return Promise.all( + changedServices.map((service) => + updateService({ + id: service.id, + companyId: id, + name: service.name, + description: service.description, + originalPrice: service.originalPrice, + limit: service.limit, + length: service.length, + color: service.color, + fontColor: service.fontColor, + fontShadowColor: service.fontShadowColor, + sessionUser: SessionInfo.userId, + showPublicScores: servicesShowPublicScoresChanged + ? servicesShowPublicScores + : service.showPublicScores !== undefined + ? service.showPublicScores + : true, + showPublicOpinions: servicesShowPublicOpinionsChanged + ? servicesShowPublicOpinions + : service.showPublicOpinions !== undefined + ? service.showPublicOpinions + : true, + }) + ) + ).then(() => { + const updatedServices = services.map((service) => ({ + ...service, + showPublicScores: servicesShowPublicScoresChanged + ? servicesShowPublicScores + : service.showPublicScores, + showPublicOpinions: servicesShowPublicOpinionsChanged + ? servicesShowPublicOpinions + : service.showPublicOpinions, + })); + + setServices(updatedServices); + setOriginalServicesShowPublicScores(servicesShowPublicScores); + setOriginalServicesShowPublicOpinions(servicesShowPublicOpinions); + alert.showSuccess("Organizacion actualizada!"); + }); }) - .catch((error: ApiError) => { - alert.showError(error.format()); + .catch((error: ApiError | Error) => { + alert.showError(error instanceof ApiError ? error.format() : error.message); }) .finally(() => { eventHandler.setEventType(EVENT_TYPES.SLEEP); @@ -484,6 +573,83 @@ export default function OrganizationEdit() { }} /> + + Visibilidad pública: Elegí si querés mostrar puntuaciones y opiniones en el perfil público y las páginas de reserva. Disponible para planes pagos. + +
+ + + {!canEditPublicRatingFlags && ( + + Disponible para planes pagos. + + )} +
+ + {services.length > 0 && ( + <> + + Visibilidad pública de servicios: Configurá si se muestran puntuaciones y opiniones en todos los servicios. Disponible para planes pagos. + +
+ + + {!canEditPublicRatingFlags && ( + + Para cambiar la visibilidad pública de los servicios necesitás un plan pago. + + )} +
+ + )} + (undefined); const [serviceDiscountValue, setServiceDiscountValue] = useState(undefined); @@ -116,6 +118,8 @@ export default function OrganizationService() { const [discountsFilterData, setDiscountsFilterData] = useState([]); const [discountId, setDiscountId] = useState(""); + const ownerSubscription = SessionInfo.organizationSubscriptions[id]; + const canEditPublicRatingFlags = (ownerSubscription?.plan.price || 0) > 0; useEffect(() => { eventHandler.setEventType(EVENT_TYPES.SLEEP); @@ -200,6 +204,12 @@ export default function OrganizationService() { limit: parseInt(serviceLimit), length: parseInt(serviceLength), sessionUser: SessionInfo.userId, + ...(canEditPublicRatingFlags + ? { + showPublicScores, + showPublicOpinions, + } + : {}), }) .then(() => { alert.showSuccess("Servicio creado"); @@ -225,6 +235,12 @@ export default function OrganizationService() { fontColor: serviceFontColor, fontShadowColor: serviceFontShadowColor, sessionUser: SessionInfo.userId, + ...(canEditPublicRatingFlags + ? { + showPublicScores, + showPublicOpinions, + } + : {}), }) .then(() => { alert.showSuccess("Servicio Actualizado"); @@ -255,6 +271,8 @@ export default function OrganizationService() { setServiceDiscountType(response.discountType); setServiceDiscountValue(response.discountValue); setServiceDiscountName(response.discountName); + setShowPublicScores(response.showPublicScores !== undefined ? response.showPublicScores : true); + setShowPublicOpinions(response.showPublicOpinions !== undefined ? response.showPublicOpinions : true); }) .catch(() => {}) .finally(() => { @@ -486,6 +504,39 @@ export default function OrganizationService() { eventHandler.setEventType(EVENT_TYPES.NEED_SAVE); }} /> + + + Visibilidad pública: Elegí si querés mostrar puntuaciones y opiniones de este servicio en páginas públicas. Disponible para planes pagos. + +
+ + + {!canEditPublicRatingFlags && ( + + Disponible para planes pagos. + + )} +
({ averageScore: 0, totalCount: 0 }); useEffect(() => { + if (organization.showPublicScores === false) { + setRatingSummary({ averageScore: 0, totalCount: 0 }); + return; + } + if (!organization.id) { setRatingSummary({ averageScore: 0, totalCount: 0 }); return; @@ -32,7 +37,7 @@ export default function OrganizationHeader(props: OrganizationHeaderProps) { .catch(() => { setRatingSummary({ averageScore: 0, totalCount: 0 }); }); - }, [organization.id]); + }, [organization.id, organization.showPublicScores]); return (
{organization.description}

- {ratingSummary.totalCount > 0 && ( + {organization.showPublicScores !== false && ratingSummary.totalCount > 0 && (
@@ -126,7 +127,12 @@ export default function ProductItem(props: ProductItemProps) {

{product.name}

- +

{ const formatScore = (score: number): string => score.toFixed(1); +const sortReviewsByScore = (reviews: RatingTargetReviewItem[]): RatingTargetReviewItem[] => { + return [...reviews].sort((firstReview, secondReview) => { + if (secondReview.score !== firstReview.score) { + return secondReview.score - firstReview.score; + } + + return new Date(secondReview.createdAt).getTime() - new Date(firstReview.createdAt).getTime(); + }); +}; + export default function ReviewsCarousel({ targetType, targetId, @@ -50,7 +60,7 @@ export default function ReviewsCarousel({ ratingTargetReviews({ targetType, targetId, limit }) .then((targetReviews) => { if (active) { - setReviews(targetReviews.filter((review) => review.comment.trim().length > 0)); + setReviews(sortReviewsByScore(targetReviews.filter((review) => review.comment.trim().length > 0))); } }) .catch(() => { diff --git a/txclient/src/app/components/ServiceRatingSummary/ServiceRatingSummary.tsx b/txclient/src/app/components/ServiceRatingSummary/ServiceRatingSummary.tsx index bd1b605..4e6b99c 100644 --- a/txclient/src/app/components/ServiceRatingSummary/ServiceRatingSummary.tsx +++ b/txclient/src/app/components/ServiceRatingSummary/ServiceRatingSummary.tsx @@ -10,13 +10,19 @@ export interface ServiceRatingSummaryProps { variant?: "card" | "row"; displayMode?: "default" | "compact"; showOpinionCount?: boolean; + enabled?: boolean; } export default function ServiceRatingSummary(props: ServiceRatingSummaryProps): React.ReactElement | null { - const { serviceId, variant = "card", displayMode = "default", showOpinionCount = true } = props; + const { serviceId, variant = "card", displayMode = "default", showOpinionCount = true, enabled = true } = props; const [ratingSummary, setRatingSummary] = useState({ averageScore: 0, totalCount: 0 }); useEffect(() => { + if (!enabled) { + setRatingSummary({ averageScore: 0, totalCount: 0 }); + return; + } + if (!serviceId) { setRatingSummary({ averageScore: 0, totalCount: 0 }); return; @@ -42,9 +48,9 @@ export default function ServiceRatingSummary(props: ServiceRatingSummaryProps): return () => { isMounted = false; }; - }, [serviceId]); + }, [serviceId, enabled]); - if (ratingSummary.totalCount <= 0) { + if (!enabled || ratingSummary.totalCount <= 0) { return null; } diff --git a/txclient/src/app/components/Switch/Switch.tsx b/txclient/src/app/components/Switch/Switch.tsx index 5f58f25..be0f534 100644 --- a/txclient/src/app/components/Switch/Switch.tsx +++ b/txclient/src/app/components/Switch/Switch.tsx @@ -5,13 +5,15 @@ import MuiSwitch from "@mui/material/Switch"; interface SwitchProps { checked: boolean; onChange: (checked: boolean) => void; + disabled?: boolean; } -export default function Switch({ checked, onChange }: SwitchProps) { +export default function Switch({ checked, onChange, disabled = false }: SwitchProps) { return ( ) => { onChange(event.target.checked); }} diff --git a/txclient/src/app/landing/org/[oid]/page.tsx b/txclient/src/app/landing/org/[oid]/page.tsx index 045f0a9..d4e3f05 100644 --- a/txclient/src/app/landing/org/[oid]/page.tsx +++ b/txclient/src/app/landing/org/[oid]/page.tsx @@ -874,13 +874,15 @@ export default function OrganizationPublicProfile() {

- + {orgData.showPublicOpinions !== false && ( + + )} )} diff --git a/txclient/src/app/landing/service/[sid]/page.tsx b/txclient/src/app/landing/service/[sid]/page.tsx index ea9d5f6..8fbcdcd 100644 --- a/txclient/src/app/landing/service/[sid]/page.tsx +++ b/txclient/src/app/landing/service/[sid]/page.tsx @@ -82,12 +82,14 @@ export default async function OrganizationPublicProfile({ params }: Props) { title="Otros Servicios 🏆" data={organizationService.organization.services.data} /> - + {organizationService.service.showPublicOpinions !== false && ( + + )} )}