feat: add showPublicScores and showPublicOpinions flags to services with plan-based access control

This commit is contained in:
2026-07-22 14:05:39 -03:00
parent da3c3abb52
commit 279e1b54cc
19 changed files with 391 additions and 43 deletions
@@ -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<CompanyServiceView[]>([]);
const [orgPublished, setOrgPublished] = useState<COMPANY_PUBLISHED_STATUS | undefined>(
undefined
@@ -65,6 +75,8 @@ export default function OrganizationEdit() {
const [savePublishedStatusAvailable, setSavePublishedStatusAvailable] = useState(false);
const [combosLoaded, setCombosLoaded] = useState<boolean>(false);
const ownerSubscription = SessionInfo.organizationSubscriptions[id];
const canEditPublicRatingFlags = (ownerSubscription?.plan.price || 0) > 0;
const [categoryFilterContent] = useState<TextObjectModalContent>({
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() {
}}
/>
<MaterialAlert severity="info" sx={{ marginTop: "20px" }}>
<b>Visibilidad pública:</b> Elegí si querés mostrar puntuaciones y opiniones en el perfil público y las páginas de reserva. Disponible para planes pagos.
</MaterialAlert>
<div style={{ display: "flex", flexDirection: "column", gap: "10px", marginTop: "10px" }}>
<label style={{ display: "flex", alignItems: "center", gap: "10px", fontWeight: 600 }}>
<Checkbox
checked={showPublicScores}
disabled={!canEditPublicRatingFlags}
onChange={(event) => {
setShowPublicScores(event.target.checked);
eventHandler.setEventType(EVENT_TYPES.NEED_SAVE);
}}
/>
Mostrar puntuaciones públicas
</label>
<label style={{ display: "flex", alignItems: "center", gap: "10px", fontWeight: 600 }}>
<Checkbox
checked={showPublicOpinions}
disabled={!canEditPublicRatingFlags}
onChange={(event) => {
setShowPublicOpinions(event.target.checked);
eventHandler.setEventType(EVENT_TYPES.NEED_SAVE);
}}
/>
Mostrar opiniones públicas
</label>
{!canEditPublicRatingFlags && (
<span style={{ color: "var(--gray-darkest)", fontSize: "13px" }}>
Disponible para planes pagos.
</span>
)}
</div>
{services.length > 0 && (
<>
<MaterialAlert severity="info" sx={{ marginTop: "20px" }}>
<b>Visibilidad pública de servicios:</b> Configurá si se muestran puntuaciones y opiniones en todos los servicios. Disponible para planes pagos.
</MaterialAlert>
<div
style={{
display: "flex",
flexDirection: "column",
gap: "10px",
marginTop: "10px",
}}
>
<label style={{ display: "flex", alignItems: "center", gap: "10px", fontWeight: 600 }}>
<Checkbox
checked={servicesShowPublicScores}
disabled={!canEditPublicRatingFlags}
onChange={(event) => {
setServicesShowPublicScores(event.target.checked);
eventHandler.setEventType(EVENT_TYPES.NEED_SAVE);
}}
/>
Mostrar puntuaciones públicas de los servicios
</label>
<label style={{ display: "flex", alignItems: "center", gap: "10px", fontWeight: 600 }}>
<Checkbox
checked={servicesShowPublicOpinions}
disabled={!canEditPublicRatingFlags}
onChange={(event) => {
setServicesShowPublicOpinions(event.target.checked);
eventHandler.setEventType(EVENT_TYPES.NEED_SAVE);
}}
/>
Mostrar opiniones públicas de los servicios
</label>
{!canEditPublicRatingFlags && (
<span style={{ color: "var(--gray-darkest)", fontSize: "13px" }}>
Para cambiar la visibilidad pública de los servicios necesitás un plan pago.
</span>
)}
</div>
</>
)}
<TextObjectFilter
placeholder="Categoría"
content={categoryFilterContent}
@@ -72,6 +72,8 @@ export default function OrganizationService() {
const [serviceColor, setServiceColor] = useState("");
const [serviceFontColor, setServiceFontColor] = useState("");
const [serviceFontShadowColor, setServiceFontShadowColor] = useState("");
const [showPublicScores, setShowPublicScores] = useState(true);
const [showPublicOpinions, setShowPublicOpinions] = useState(true);
const [serviceDiscountType, setServiceDiscountType] = useState<string | undefined>(undefined);
const [serviceDiscountValue, setServiceDiscountValue] = useState<number | undefined>(undefined);
@@ -116,6 +118,8 @@ export default function OrganizationService() {
const [discountsFilterData, setDiscountsFilterData] = useState<TextObjectFilterResult[]>([]);
const [discountId, setDiscountId] = useState<string>("");
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);
}}
/>
<MaterialAlert severity="info" sx={{ marginTop: "20px" }}>
<b>Visibilidad pública:</b> Elegí si querés mostrar puntuaciones y opiniones de este servicio en páginas públicas. Disponible para planes pagos.
</MaterialAlert>
<div style={{ display: "flex", flexDirection: "column", gap: "10px", marginTop: "10px" }}>
<label style={{ display: "flex", alignItems: "center", gap: "10px", fontWeight: 600 }}>
<Switch
checked={showPublicScores}
disabled={!canEditPublicRatingFlags}
onChange={(checked) => {
setShowPublicScores(checked);
eventHandler.setEventType(EVENT_TYPES.NEED_SAVE);
}}
/>
Mostrar puntuaciones públicas
</label>
<label style={{ display: "flex", alignItems: "center", gap: "10px", fontWeight: 600 }}>
<Switch
checked={showPublicOpinions}
disabled={!canEditPublicRatingFlags}
onChange={(checked) => {
setShowPublicOpinions(checked);
eventHandler.setEventType(EVENT_TYPES.NEED_SAVE);
}}
/>
Mostrar opiniones públicas
</label>
{!canEditPublicRatingFlags && (
<span style={{ color: "var(--gray-darkest)", fontSize: "13px" }}>
Disponible para planes pagos.
</span>
)}
</div>
<Textbox
name="servicePrice"
placeholder="Precio del servicio"