Files
turnosxpress/txclient/src/app/landing/org/[oid]/page.tsx
T

909 lines
41 KiB
TypeScript

"use client";
import style from "./profile.module.css";
//import ProductGallery from "@components/Home/ProductsGallery/ProductGallery";
import WhatsAppIcon from "@mui/icons-material/WhatsApp";
import LocationOnIcon from "@mui/icons-material/LocationOn";
import LocationMap from "@core/app/components/LocationMap/LocationMap";
import Button from "@core/app/components/Button/Button";
import { useNavigation } from "@core/app/hooks/goto";
import { useOrgDataStore } from "@core/Store/OrganizationData.Store";
import { useEffect, useRef, useState } from "react";
import { PublicOrganizationView } from "@core/Models/Company.model";
import { blurElementsOnLoad } from "@core/helpers/blur";
import { ButtonGroup, IconButton } from "@mui/material";
import GridViewOutlinedIcon from "@mui/icons-material/GridViewOutlined";
import SplitscreenOutlinedIcon from "@mui/icons-material/SplitscreenOutlined";
import ProductItem, { FindResultsViewMode } from "@core/app/components/ProductItem/ProductItem";
import {
FacebookShareButton,
EmailShareButton,
WhatsappShareButton,
TelegramShareButton,
TwitterShareButton,
FacebookIcon,
WhatsappIcon as WhatsAppShareIcon,
EmailIcon,
TelegramIcon,
TwitterIcon,
} from "react-share";
import HeaderConfProvider from "@core/app/components/HeaderConfProvider/HeaderConfProvider";
import useWindowSize from "@core/app/hooks/WindowSize";
import AddIcon from "@mui/icons-material/AddOutlined";
import CloseIcon from "@mui/icons-material/Close";
import QuillEditor from "@core/app/components/QuillEditor/QuillEditor";
import {
deletePost,
findPostsByOrganization,
getPost,
moveFixedPost,
savePost,
toggleFixedPost,
} from "./post.service";
import { useSessionStore } from "@core/Store/Sesion.Store";
import { EVENT_TYPES, useEventHandlerStore } from "@core/Store/EventHandler.Store";
import { useAlert } from "@core/Store/Alert.Store";
import { IPost } from "@core/Models/Posts.model";
import { preprocessQuillHtml, transformQuillContentForDisplay } from "@core/app/helpers/htmlSainitize";
import { useSearchParams } from "next/navigation";
import { ThemeProvider } from "@emotion/react";
import MaterialButton from "@mui/material/Button";
import ClipIcon from "@mui/icons-material/AttachFileRounded";
import DeleteIcon from "@mui/icons-material/DeleteOutlineRounded";
import UpIcon from "@mui/icons-material/ArrowUpwardRounded";
import DownIcon from "@mui/icons-material/ArrowDownwardRounded";
import PinIcon from "@mui/icons-material/PushPin";
import EditIcon from "@mui/icons-material/AutoFixHigh";
import turnosXpressTheme from "@core/app/theme/turnosXpress";
import { useParams } from "next/navigation";
import dayjs from "dayjs";
import { useConfirmStore } from "@core/Store/Confirm.Store";
import ReviewsCarousel from "@core/app/components/ReviewsCarousel/ReviewsCarousel";
import OrganizationProfessionals from "@core/app/components/OrganizationProfessionals/OrganizationProfessionals";
import { RatingTargetType } from "@core/Models/Ratings.model";
enum VIEW {
POSTS = "posts",
SERVICES = "services",
OPINIONS = "opinions",
INFO = "info",
}
type UrlData = {
oid: string;
};
export default function OrganizationPublicProfile() {
const data = useParams<UrlData>();
const oid = data.oid;
const OrganizationData = useOrgDataStore();
const [position, setPosition] = useState<[number, number]>([0, 0]);
const [orgData, setOrgData] = useState<PublicOrganizationView>();
const [viewMode, setViewMode] = useState<FindResultsViewMode>("grid");
const [shareTitle, setShareTitle] = useState<string>("");
const [view, setView] = useState<VIEW>(VIEW.POSTS);
const [wndWidth, wndHeight] = useWindowSize();
const { goTo } = useNavigation();
const refDivContent = useRef<HTMLDivElement>(null);
const refPostsContainer = useRef<HTMLDivElement>(null);
const [showAddButton, setShowAddButton] = useState<boolean>(false);
const [showEditor, setShowEditor] = useState<boolean>(false);
const [firstLoad, setFirstLoad] = useState<boolean>(true);
const [editorPostId, setEditorPostId] = useState<string>("");
const [editorContent, setEditorContent] = useState("");
const [posts, setPosts] = useState<IPost[]>([]);
const [authorizedUsers, setAuthorizedUsers] = useState<string[]>([]);
const [currentPage, setCurrentPage] = useState<number>(1);
const SessionInfo = useSessionStore();
const eventHandler = useEventHandlerStore();
const alert = useAlert();
const confirm = useConfirmStore();
const searchParams = useSearchParams();
const tab = searchParams.get("tab") || "posts";
useEffect(() => {
blurElementsOnLoad();
if (!tab) {
setView(VIEW.POSTS);
return;
}
switch (tab) {
case "posts":
setView(VIEW.POSTS);
break;
case "services":
setView(VIEW.SERVICES);
break;
case "opinions":
setView(VIEW.OPINIONS);
break;
case "info":
setView(VIEW.INFO);
break;
default:
setView(VIEW.POSTS);
break;
}
}, []);
useEffect(() => {
requestAnimationFrame(() => {
setTimeout(() => {
processAddButton();
}, 1000);
});
}, [SessionInfo, authorizedUsers]);
useEffect(() => {
if (!OrganizationData.orgData) return;
if (!oid) return; //if (!OrganizationData.orgData.id) return;
setOrgData(OrganizationData.orgData);
setPosition([OrganizationData.orgData.latitude, OrganizationData.orgData.longitude]);
setShareTitle(OrganizationData.orgData.name + " - " + OrganizationData.orgData.description);
setCurrentPage(1);
setPosts([]);
cargarPosts(OrganizationData.orgData.id, 1);
}, [OrganizationData.orgData]);
useEffect(() => {
if (orgData && oid) {
//if (orgData && orgData.id) {
cargarPosts(orgData.id, currentPage);
}
}, [currentPage, orgData]);
useEffect(() => {
const handleScroll = () => {
if (refPostsContainer.current) {
const { scrollTop, scrollHeight, clientHeight } = refPostsContainer.current;
const isAtBottom = scrollTop + clientHeight >= scrollHeight - 50;
if (isAtBottom) {
setCurrentPage((prevPage) => prevPage + 1);
}
}
};
const currentRef = refPostsContainer.current;
if (currentRef) {
currentRef.addEventListener("scroll", handleScroll);
}
return () => {
if (currentRef) {
currentRef.removeEventListener("scroll", handleScroll);
}
};
}, [refPostsContainer.current, view, currentPage]);
const processAddButton = (currentView: VIEW = view) => {
if (currentView != VIEW.POSTS) {
setShowAddButton(false);
return;
}
if (!SessionInfo.userId) {
setShowAddButton(false);
return;
}
if (!authorizedUsers.includes(SessionInfo.userId)) {
setShowAddButton(false);
return;
}
setShowAddButton(true);
};
const checkPostButtons = (): boolean => {
if (!SessionInfo.userId) {
return false;
}
if (authorizedUsers.includes(SessionInfo.userId)) {
return true;
}
return false;
};
const checkShowInfo = (): boolean => {
if (wndWidth < 800) {
return true;
}
if (view === VIEW.INFO) {
setView(VIEW.POSTS);
}
return false;
};
const getContentHeight = () => {
if (!refDivContent.current) return 0;
const containerSize = refDivContent.current.getClientRects().item(0);
if (!containerSize) return 0;
return wndHeight - containerSize.y - 90;
};
const getHeaderItemStyle = (item: VIEW): React.CSSProperties => {
const returnValue: React.CSSProperties = {};
returnValue.fontWeight = "bold";
returnValue.color = "var(--wine-black)";
returnValue.height = "100%";
returnValue.display = "flex";
returnValue.alignItems = "center";
returnValue.padding = "20px";
returnValue.cursor = "pointer";
if (item === view) {
returnValue.borderBottom = "3px solid var(--wine-red)";
}
return returnValue;
};
const getAddButtonStyle = (): React.CSSProperties => {
const returnValue: React.CSSProperties = {};
const buttonWidth = 64;
const buttonMargin = 40;
returnValue.position = "absolute";
returnValue.backgroundColor = "var(--wine-dark)";
returnValue.width = `${buttonWidth}px`;
returnValue.height = `${buttonWidth}px`;
returnValue.borderRadius = "50%";
returnValue.display = "flex";
returnValue.justifyContent = "center";
returnValue.alignItems = "center";
returnValue.cursor = "pointer";
returnValue.color = "var(--white)";
returnValue.zIndex = 10;
returnValue.right = `${buttonMargin}px`;
returnValue.bottom = `${buttonMargin}px`;
return returnValue;
};
const getEditorStyle = (): React.CSSProperties => {
const returnValue: React.CSSProperties = {};
returnValue.position = "fixed";
returnValue.backgroundColor = "var(--white)";
returnValue.top = "60px";
returnValue.left = "0px";
returnValue.right = "0px";
returnValue.bottom = "0px";
returnValue.color = "var(--black)";
returnValue.zIndex = 9999;
returnValue.display = "flex";
returnValue.flexDirection = "column";
return returnValue;
};
const crearPost = (content: string) => {
setShowEditor(false);
eventHandler.setEventType(EVENT_TYPES.LOADING);
savePost({
content: content,
companyId: orgData!.id,
postId: editorPostId ? editorPostId : undefined,
sessionUser: SessionInfo.userId,
})
.then(() => {
alert.showSuccess("Post creado");
// After creating a post, reset and reload the first page
setCurrentPage(1);
setPosts([]);
if (orgData) {
cargarPosts(orgData.id, 1);
}
})
.catch((error) => {
alert.showError(error.format());
})
.finally(() => {
setEditorContent("");
eventHandler.setEventType(EVENT_TYPES.SLEEP);
});
};
const cargarPosts = (companyId: string, page: number) => {
if (!companyId) {
return;
}
findPostsByOrganization({
companyId: companyId,
page: page,
limit: 10,
sessionUser: SessionInfo.userId,
})
.then((response) => {
setAuthorizedUsers(response.authorizedUsers);
requestAnimationFrame(() => {
setPosts((prevPosts) => {
if (page === 1) {
return response.data;
}
const newPosts = response.data.filter(
(newPost) => !prevPosts.some((prevPost) => prevPost._id === newPost._id),
);
return [...prevPosts, ...newPosts];
});
requestAnimationFrame(() => {
processAddButton();
});
});
if (firstLoad) {
if (view === VIEW.POSTS && response.data.length == 0) {
requestAnimationFrame(() => {
setView(VIEW.SERVICES);
processAddButton();
});
}
}
setFirstLoad(false);
})
.catch((error) => {
console.log(error.format());
alert.showError(error.format());
});
};
const fijarPost = (postId: string) => {
if (!orgData) {
alert.showError("No se pudo determinar la organización.");
return;
}
eventHandler.setEventType(EVENT_TYPES.LOADING);
toggleFixedPost({
companyId: orgData.id,
postId: postId,
sessionUser: SessionInfo.userId,
})
.then(() => {
alert.showSuccess("Post actualizado");
// After creating a post, reset and reload the first page
setCurrentPage(1);
setPosts([]);
if (orgData) {
cargarPosts(oid, 1); //cargarPosts(orgData.id, 1);
}
})
.catch((error) => {
alert.showError(error.format());
})
.finally(() => {
eventHandler.setEventType(EVENT_TYPES.SLEEP);
});
};
const moverPost = (postId: string, moveLength: number) => {
if (!orgData) {
alert.showError("No se pudo determinar la organización.");
return;
}
eventHandler.setEventType(EVENT_TYPES.LOADING);
moveFixedPost({
companyId: orgData.id,
postId: postId,
moveLength: moveLength,
sessionUser: SessionInfo.userId,
})
.then(() => {
alert.showSuccess("Post actualizado");
// After creating a post, reset and reload the first page
setCurrentPage(1);
setPosts([]);
if (orgData) {
cargarPosts(oid, 1); //cargarPosts(orgData.id, 1);
}
})
.catch((error) => {
alert.showError(error.format());
})
.finally(() => {
eventHandler.setEventType(EVENT_TYPES.SLEEP);
});
};
const eliminarPost = (postId: string) => {
if (!orgData) {
alert.showError("No se pudo determinar la organización.");
return;
}
confirm.show(
"¿Estás seguro de eliminar el post?",
() => {
eventHandler.setEventType(EVENT_TYPES.LOADING);
deletePost({
companyId: orgData.id,
id: postId,
sessionUser: SessionInfo.userId,
})
.then(() => {
setPosts((prevPosts) => prevPosts.filter((post) => post._id !== postId));
alert.showSuccess("Post eliminado");
})
.catch((error) => {
alert.showError(error.format());
})
.finally(() => {
eventHandler.setEventType(EVENT_TYPES.SLEEP);
});
},
() => {},
);
};
const editarPost = (postId: string) => {
getPost({
id: postId,
})
.then((postData) => {
setEditorPostId(postId);
setEditorContent(postData.content);
setShowEditor(true);
})
.catch((error) => {
alert.showError(error.format());
});
};
const LocationInfo = (data: PublicOrganizationView | null) => {
if (!data) {
return null;
}
return (
<>
<h2>Información Útil</h2>
<LocationMap position={position} orgData={data} />
<ul>
<li>
<LocationOnIcon />
{data.address}
</li>
<li>
<WhatsAppIcon />
{data.phone}
</li>
</ul>
<Button
color="primary"
text="Enviar mensaje"
width="100%"
onClick={() => {
goTo(`/landing/org/${data.id}/send-message`);
}}
/>
<div
style={{
display: "flex",
justifyContent: "center",
flexDirection: "row",
alignItems: "center",
gap: "10px",
}}
>
<WhatsappShareButton title={shareTitle} separator=":: " url={window.location.href}>
<WhatsAppShareIcon size={32} round />
</WhatsappShareButton>
<FacebookShareButton title={shareTitle} url={window.location.href}>
<FacebookIcon size={32} round />
</FacebookShareButton>
<EmailShareButton title={shareTitle} separator=":: " url={window.location.href}>
<EmailIcon size={32} round />
</EmailShareButton>
<TelegramShareButton title={shareTitle} url={window.location.href}>
<TelegramIcon size={32} round />
</TelegramShareButton>
<TwitterShareButton title={shareTitle} url={window.location.href}>
<TwitterIcon size={32} round />
</TwitterShareButton>
</div>
</>
);
};
return (
<>
<HeaderConfProvider />
{orgData && (
<div className={style.organizationContent}>
{showEditor && (
<div style={getEditorStyle()}>
{/* Header */}
<div
style={{
height: "50px",
backgroundColor: "#f0f0f0",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
paddingLeft: " 20px",
borderBottom: "1px solid #ccc",
}}
>
<h2>Qué estas pensando?</h2>
<IconButton
color="inherit"
style={{ width: "64px", height: "64px" }}
onClick={() => {
setShowEditor(false);
}}
>
<CloseIcon sx={{ ml: 1, mr: 1 }} />
</IconButton>
</div>
{/* Content (Editor) */}
<div
style={{
flexGrow: 1,
display: "flex",
flexDirection: "column",
overflow: "auto",
}}
>
<QuillEditor value={editorContent} onChange={setEditorContent} />
</div>
{/* Bottom Bar */}
<div
style={{
height: "60px",
backgroundColor: "#f0f0f0",
borderTop: "1px solid #ccc",
padding: "0 20px",
display: "flex",
alignItems: "center",
}}
>
<Button
text={editorPostId ? "Actualizar" : "Publicar"}
width="100%"
onClick={() => {
setShowEditor(false);
crearPost(editorContent);
}}
color="success"
></Button>
</div>
</div>
)}
<div className={style.organizationProfileColumns}>
<div className={style.organizationMap}>{LocationInfo(orgData)}</div>
<div className={style.organizationMainColumn}>
<div
ref={refDivContent}
style={{
border: "1px solid var(--gray-light)",
borderRadius: "10px",
marginTop: "30px",
width: "100%",
marginBottom: "30px",
backgroundColor: "var(--white)",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
borderBottom: "1px solid var(--gray-light)",
}}
>
<div
style={{
height: "50px",
display: "flex",
alignItems: "center",
gap: "20px",
}}
>
<div
style={getHeaderItemStyle(VIEW.POSTS)}
onClick={() => {
setView(VIEW.POSTS);
processAddButton(VIEW.POSTS);
}}
>
Posts
</div>
<div
style={getHeaderItemStyle(VIEW.SERVICES)}
onClick={() => {
setView(VIEW.SERVICES);
processAddButton(VIEW.SERVICES);
}}
>
Servicios
</div>
{checkShowInfo() && (
<div
style={getHeaderItemStyle(VIEW.INFO)}
onClick={() => {
setView(VIEW.INFO);
processAddButton(VIEW.INFO);
}}
>
Contacto
</div>
)}
</div>
{view === VIEW.SERVICES && (
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<ButtonGroup
variant="outlined"
aria-label="Basic button group"
sx={{ marginTop: "10px", marginRight: "30px" }}
>
<IconButton onClick={() => setViewMode("card")}>
<GridViewOutlinedIcon
sx={{
color:
viewMode === "card"
? "var(--wine-red)"
: "ineherit",
}}
/>
</IconButton>
<IconButton onClick={() => setViewMode("grid")}>
<SplitscreenOutlinedIcon
sx={{
color:
viewMode === "grid"
? "var(--wine-red)"
: "ineherit",
}}
/>
</IconButton>
</ButtonGroup>
</div>
)}
</div>
<div>
{view === VIEW.POSTS && (
<div
ref={refPostsContainer}
style={{
overflowY: "scroll",
height: `${getContentHeight()}px`,
}}
>
{showAddButton && (
<div
style={getAddButtonStyle()}
onClick={() => {
setEditorPostId("");
setEditorContent("");
setShowEditor(true);
}}
>
<AddIcon sx={{ fontSize: "32px" }} />
</div>
)}
{posts.length === 0 && (
<div
style={{
textAlign: "center",
backgroundImage: "url(/no-conversation.webp)",
backgroundRepeat: "no-repeat",
backgroundPosition: "center center",
backgroundSize: "50% auto",
height: "100%",
}}
>
Nada por aquí
</div>
)}
{posts.map((post: IPost) => {
return (
<div
key={post._id}
style={{
borderBottom: "1px solid var(--gray-light)",
paddingTop: "5px",
paddingBottom: "10px",
paddingLeft: "30px",
paddingRight: "30px",
}}
>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
}}
>
<div style={{ color: "var(--gray-darkest)" }}>
{dayjs(post.createdAt).format(
"DD MMMM YYYY HH:mm",
)}
</div>
{post.fixed && (
<div
style={{
color: "var(--gray-light)",
display: "flex",
flexDirection: "row",
alignItems: "center",
}}
>
<PinIcon fontSize="small" />
<span>Anclado</span>
</div>
)}
</div>
<div
dangerouslySetInnerHTML={{
__html: preprocessQuillHtml(
transformQuillContentForDisplay(
post.content,
),
),
}}
/>
{checkPostButtons() && (
<div
style={{
marginTop: "10px",
display: "flex",
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-end",
flexWrap: "wrap",
color: "var(--gray-darkest)",
}}
>
<ThemeProvider theme={turnosXpressTheme}>
<MaterialButton
color="inherit"
startIcon={<ClipIcon />}
onClick={() => {
fijarPost(`${post._id}`);
}}
>
{post.fixed ? "Liberar" : "Fijar"}
</MaterialButton>
{post.fixed && (
<>
<MaterialButton
color="inherit"
startIcon={<UpIcon />}
onClick={() => {
moverPost(
`${post._id}`,
-1,
);
}}
>
Subir
</MaterialButton>
<MaterialButton
color="inherit"
startIcon={<DownIcon />}
onClick={() => {
moverPost(
`${post._id}`,
1,
);
}}
>
Bajar
</MaterialButton>
</>
)}
<MaterialButton
color="inherit"
onClick={() => {
editarPost(`${post._id}`);
}}
>
<EditIcon />
</MaterialButton>
<MaterialButton
color="inherit"
onClick={() => {
eliminarPost(`${post._id}`);
}}
>
<DeleteIcon />
</MaterialButton>
</ThemeProvider>
</div>
)}
</div>
);
})}
</div>
)}
{view === VIEW.SERVICES && (
<div
className={style.organizationServices}
style={{
overflowY: "scroll",
height: `${getContentHeight()}px`,
}}
>
<div
style={{
padding: "0px 0px 40px 0px",
display: "flex",
flexDirection: viewMode === "card" ? "row" : "column",
justifyContent: "start",
gap: "20px",
flexWrap: "wrap",
}}
>
{orgData.services.data.map((service) => (
<ProductItem
product={service}
key={service.id}
width={260}
viewMode={viewMode}
/>
))}
</div>
</div>
)}
{view === VIEW.INFO && checkShowInfo() && (
<div
className={style.organizationMoreInfo}
style={{
overflowY: "auto",
}}
>
{LocationInfo(orgData)}
</div>
)}
</div>
</div>
</div>
</div>
{orgData.showPublicOpinions !== false && (
<ReviewsCarousel
targetType={RatingTargetType.COMPANY}
targetId={orgData.id}
title={`Opiniones sobre ${orgData.name}`}
subtitle="Opiniones reales de personas que reservaron turnos acá."
className={style.organizationReviews}
/>
)}
{orgData.showPublicProfessionals !== false && (
<OrganizationProfessionals companyId={orgData.id} />
)}
</div>
)}
</>
);
}