feat: add appointment notification sharing functionality via WhatsApp, email, and system clipboard
This commit is contained in:
@@ -39,6 +39,13 @@ export type SendAppointmentNotificationParams = {
|
||||
type: APPOINTMENT_NOTIFICATION_TYPE;
|
||||
};
|
||||
|
||||
export type AppointmentNotificationPreviewResult = {
|
||||
subject: string;
|
||||
message: string;
|
||||
emailMessage: string;
|
||||
wapMessage: string;
|
||||
};
|
||||
|
||||
export type DeleteAppointmentParams = {
|
||||
id: string;
|
||||
validation?: boolean;
|
||||
|
||||
+18
@@ -1,6 +1,7 @@
|
||||
import ApiRequest from "@services/Api.Service";
|
||||
import {
|
||||
ApplyAppointmentDiscountParams,
|
||||
AppointmentNotificationPreviewResult,
|
||||
AppointmentAdminByClientView,
|
||||
AppointmentEventByClient,
|
||||
DeleteAppointmentDiscountParams,
|
||||
@@ -108,6 +109,23 @@ export const createImmediateNotificationJobs = async (
|
||||
});
|
||||
};
|
||||
|
||||
export const getAppointmentNotificationPreview = async (
|
||||
data: SendAppointmentNotificationParams
|
||||
): Promise<AppointmentNotificationPreviewResult> => {
|
||||
return new Promise<AppointmentNotificationPreviewResult>((resolve, reject) => {
|
||||
schemaSendWapNotification
|
||||
.validate(data, { abortEarly: true })
|
||||
.then(() => {
|
||||
resolve(
|
||||
ApiRequest.post<AppointmentNotificationPreviewResult>("appointments/notification-preview", data)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(new ApiError(500, error.message));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const schemaApplyDiscount = Yup.object().shape({
|
||||
appointmentId: Yup.string().required("Debe seleccionar un turno."),
|
||||
companyId: Yup.string().required("Organización no seleccionada."),
|
||||
|
||||
+158
@@ -13,12 +13,14 @@ import {
|
||||
deleteAppointment,
|
||||
deleteAppointmentDiscount,
|
||||
findAppointmentsAdminByClient,
|
||||
getAppointmentNotificationPreview,
|
||||
updateAppointment,
|
||||
} from "../Appointments.Service";
|
||||
import {
|
||||
ApplyAppointmentDiscountParams,
|
||||
APPOINTMENT_NOTIFICATION_TYPE,
|
||||
AppointmentEventByClient,
|
||||
AppointmentNotificationPreviewResult,
|
||||
DeleteAppointmentDiscountParams,
|
||||
UpdateAppointmentParams,
|
||||
} from "@core/Models/Appointments.model";
|
||||
@@ -27,6 +29,10 @@ import Textbox from "@core/app/components/Textbox/Textbox";
|
||||
import { useMenuStore } from "@core/Store/Menu.Store";
|
||||
import NotificationsActiveOutlinedIcon from "@mui/icons-material/NotificationsActiveOutlined";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
||||
import EmailIcon from "@mui/icons-material/Email";
|
||||
import ShareIcon from "@mui/icons-material/Share";
|
||||
import WhatsAppIcon from "@mui/icons-material/WhatsApp";
|
||||
|
||||
import Switch from "@mui/material/Switch";
|
||||
import { ThemeProvider } from "@emotion/react";
|
||||
@@ -91,6 +97,8 @@ export default function ClientAppointment() {
|
||||
const [comments, setComments] = useState<string>("");
|
||||
|
||||
const [dialogDiscountVisible, setDialogDiscountVisible] = useState(false);
|
||||
const [dialogShareVisible, setDialogShareVisible] = useState(false);
|
||||
const [sharePreview, setSharePreview] = useState<AppointmentNotificationPreviewResult | undefined>(undefined);
|
||||
|
||||
const [discountsFilterContent] = useState<TextObjectModalContent>({
|
||||
header: (
|
||||
@@ -361,6 +369,14 @@ export default function ClientAppointment() {
|
||||
icon: <NotificationsActiveOutlinedIcon />,
|
||||
});
|
||||
|
||||
menu.add({
|
||||
text: "Compartir",
|
||||
onClick: () => {
|
||||
openShareDialog();
|
||||
},
|
||||
icon: <ShareIcon />,
|
||||
});
|
||||
|
||||
if (showQuitDiscount) {
|
||||
menu.divider();
|
||||
|
||||
@@ -408,6 +424,66 @@ export default function ClientAppointment() {
|
||||
});
|
||||
};
|
||||
|
||||
const openShareDialog = () => {
|
||||
if (!appointmentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setDialogShareVisible(true);
|
||||
eventHandler.setEventType(EVENT_TYPES.LOADING);
|
||||
getAppointmentNotificationPreview({
|
||||
appointmentId: appointmentId.toString(),
|
||||
type: APPOINTMENT_NOTIFICATION_TYPE.REMINDER,
|
||||
sessionUser: SessionUser.userId,
|
||||
})
|
||||
.then((preview) => {
|
||||
setSharePreview(preview);
|
||||
})
|
||||
.catch((error) => {
|
||||
alert.showError(error.format());
|
||||
setDialogShareVisible(false);
|
||||
})
|
||||
.finally(() => {
|
||||
eventHandler.setEventType(EVENT_TYPES.SLEEP);
|
||||
});
|
||||
};
|
||||
|
||||
const copyShareText = async () => {
|
||||
if (!sharePreview) {
|
||||
return;
|
||||
}
|
||||
|
||||
await navigator.clipboard.writeText(sharePreview.wapMessage);
|
||||
alert.showSuccess("Texto copiado al portapapeles");
|
||||
};
|
||||
|
||||
const shareByWhatsApp = () => {
|
||||
if (!sharePreview) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.open(`https://wa.me/?text=${encodeURIComponent(sharePreview.wapMessage)}`, "_blank");
|
||||
};
|
||||
|
||||
const shareByEmail = () => {
|
||||
if (!sharePreview) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.href = `mailto:?subject=${encodeURIComponent(sharePreview.subject)}&body=${encodeURIComponent(sharePreview.emailMessage)}`;
|
||||
};
|
||||
|
||||
const nativeShare = async () => {
|
||||
if (!sharePreview || !navigator.share) {
|
||||
return;
|
||||
}
|
||||
|
||||
await navigator.share({
|
||||
title: sharePreview.subject,
|
||||
text: sharePreview.wapMessage,
|
||||
});
|
||||
};
|
||||
|
||||
const applyDiscountHandler = () => {
|
||||
if (!SessionUser.userId) {
|
||||
return;
|
||||
@@ -728,6 +804,88 @@ export default function ClientAppointment() {
|
||||
</>
|
||||
</DialogWindow>
|
||||
|
||||
<DialogWindow
|
||||
visible={dialogShareVisible}
|
||||
setVisible={setDialogShareVisible}
|
||||
>
|
||||
<>
|
||||
<DialogContent
|
||||
style={{
|
||||
maxWidth: "520px",
|
||||
minWidth: "300px",
|
||||
margin: "auto",
|
||||
}}
|
||||
>
|
||||
<h3 style={{ marginBottom: "10px" }}>Compartir recordatorio</h3>
|
||||
<p style={{ fontSize: "14px", fontWeight: 500, marginBottom: "10px" }}>
|
||||
Este texto usa el mismo contenido del recordatorio de notificaciones.
|
||||
</p>
|
||||
<textarea
|
||||
readOnly
|
||||
value={sharePreview?.wapMessage || ""}
|
||||
style={{
|
||||
width: "100%",
|
||||
minHeight: "180px",
|
||||
border: "1px solid var(--gray)",
|
||||
borderRadius: "8px",
|
||||
color: "var(--black)",
|
||||
fontSize: "14px",
|
||||
padding: "10px",
|
||||
}}
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions style={{ flexWrap: "wrap", gap: "8px" }}>
|
||||
<ButtonMaterial
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<WhatsAppIcon />}
|
||||
disabled={!sharePreview}
|
||||
onClick={shareByWhatsApp}
|
||||
>
|
||||
WhatsApp
|
||||
</ButtonMaterial>
|
||||
<ButtonMaterial
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<EmailIcon />}
|
||||
disabled={!sharePreview}
|
||||
onClick={shareByEmail}
|
||||
>
|
||||
Email
|
||||
</ButtonMaterial>
|
||||
<ButtonMaterial
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<ContentCopyIcon />}
|
||||
disabled={!sharePreview}
|
||||
onClick={copyShareText}
|
||||
>
|
||||
Copiar
|
||||
</ButtonMaterial>
|
||||
{typeof navigator !== "undefined" && !!navigator.share && (
|
||||
<ButtonMaterial
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<ShareIcon />}
|
||||
disabled={!sharePreview}
|
||||
onClick={nativeShare}
|
||||
>
|
||||
Compartir
|
||||
</ButtonMaterial>
|
||||
)}
|
||||
<ButtonMaterial
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
setDialogShareVisible(false);
|
||||
}}
|
||||
>
|
||||
Cerrar
|
||||
</ButtonMaterial>
|
||||
</DialogActions>
|
||||
</>
|
||||
</DialogWindow>
|
||||
|
||||
<DialogWindow
|
||||
visible={dialogChangeServiceVisible}
|
||||
setVisible={setDialogChangeServiceVisible}
|
||||
|
||||
@@ -159,6 +159,15 @@ export default function UserMenu(): React.ReactElement {
|
||||
goTo(`/admin/org/profile/${orgId}/appointments/${targetDate}`);
|
||||
};
|
||||
|
||||
const handleDownloadClick = () => {
|
||||
toggleMenu();
|
||||
window.location.href = "/download/tx-latest.apk";
|
||||
};
|
||||
|
||||
const userBelongsToOrganization = () => {
|
||||
return Object.keys(SessionInfo.organizationSubscriptions || {}).length > 0;
|
||||
};
|
||||
|
||||
const getUserName = () => {
|
||||
return SessionInfo.loged ? SessionInfo.userFullName : "Anonimo";
|
||||
};
|
||||
@@ -263,6 +272,15 @@ export default function UserMenu(): React.ReactElement {
|
||||
iconAlign="left"
|
||||
onClick={() => goTo("/messenger")}
|
||||
/>
|
||||
{userBelongsToOrganization() && (
|
||||
<ButtonOption
|
||||
text="Download"
|
||||
icon="/download-icon.svg"
|
||||
textAlign="left"
|
||||
iconAlign="left"
|
||||
onClick={handleDownloadClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!SessionInfo.loged && (
|
||||
|
||||
Reference in New Issue
Block a user