From b03be631b181cc07f30b170fec6bf48b9dafa03f Mon Sep 17 00:00:00 2001 From: zant Date: Wed, 30 Oct 2024 20:32:56 +0100 Subject: [PATCH] fix: render community section --- src/components/CommentBox.tsx | 86 +++++ src/components/PostBox.tsx | 85 +++++ src/components/RiskChart.tsx | 48 +++ src/components/SitesReport.tsx | 22 ++ src/components/list/RankViewBox.tsx | 81 +++++ .../locales/en/{myCity.json => feed.json} | 7 +- .../locales/es/{myCity.json => feed.json} | 7 +- .../locales/pt/{myCity.json => feed.json} | 7 +- src/layout/AppBar.tsx | 3 +- src/pages/my-city/MyCityPage.tsx | 342 +----------------- src/pages/my-community/MyCommunityPage.tsx | 132 +++++++ src/routes/AppRouter.tsx | 5 + src/types/resources.ts | 4 +- 13 files changed, 489 insertions(+), 340 deletions(-) create mode 100644 src/components/CommentBox.tsx create mode 100644 src/components/PostBox.tsx create mode 100644 src/components/RiskChart.tsx create mode 100644 src/components/SitesReport.tsx create mode 100644 src/components/list/RankViewBox.tsx rename src/i18n/locales/en/{myCity.json => feed.json} (75%) rename src/i18n/locales/es/{myCity.json => feed.json} (76%) rename src/i18n/locales/pt/{myCity.json => feed.json} (75%) create mode 100644 src/pages/my-community/MyCommunityPage.tsx diff --git a/src/components/CommentBox.tsx b/src/components/CommentBox.tsx new file mode 100644 index 0000000..69e14bf --- /dev/null +++ b/src/components/CommentBox.tsx @@ -0,0 +1,86 @@ +import { Box } from '@mui/material'; +import useAxios from 'axios-hooks'; +import { deserialize, DocumentObject } from 'jsonapi-fractal'; +import { useEffect, useState } from 'react'; +import ThumbsUp from '@/assets/icons/thumbs-up.svg'; +import useLangContext from '@/hooks/useLangContext'; +import Loader from '@/themed/loader/Loader'; +import Text from '@/themed/text/Text'; +import { formatDateFromString } from '@/util'; + +interface CommentBoxProps { + postId: number; +} + +interface IComment { + content: string; + createdAt: string; + id: number; + likesCount: number; + photos?: string; + postId: number; + updatedAt: string; + canDeleteByUser: boolean; + canEditByUser: boolean; + createdBy: ICommentUser; +} + +interface ICommentUser { + accountId: number; + lastName: string; + userName: string; +} + +const CommentBox = ({ postId }: CommentBoxProps) => { + const [{ data, loading, error }] = useAxios({ + url: `/posts/${postId}`, + }); + const [comments, setComments] = useState([]); + const langContext = useLangContext(); + + useEffect(() => { + if (!data) return; + const deserializedData = deserialize<{ comments: DocumentObject[] }>(data) as { comments: DocumentObject[] }[]; + const first = deserializedData.shift(); + if (first) setComments(first.comments); + }, [data]); + + return ( + <> + {loading && } + {!loading && + comments.map((commentData) => { + const comment = deserialize(commentData) as IComment; + const acronym = `${comment.createdBy.userName[0]}${comment.createdBy.lastName[0]}`; + + return ( + + + + {acronym} + + + + + {comment.createdBy.userName} {comment.createdBy.lastName} + + + {formatDateFromString(langContext.state.selected, comment.createdAt)} + + + {comment.content} + + success + {comment.likesCount} + + + + {error &&

Something wrong happened

} +
+ ); + })} + + ); +}; + +export default CommentBox; diff --git a/src/components/PostBox.tsx b/src/components/PostBox.tsx new file mode 100644 index 0000000..e2c12d7 --- /dev/null +++ b/src/components/PostBox.tsx @@ -0,0 +1,85 @@ +import { Box } from '@mui/material'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import Comment from '@/assets/icons/comment.svg'; +import ThumbsUp from '@/assets/icons/thumbs-up.svg'; +import Trash from '@/assets/icons/trash.svg'; +import Text from '@/themed/text/Text'; +import CommentBox from './CommentBox'; + +interface PostProps { + id: number; + author: string; + date: number; + location: string; + text: string; + image?: { photo_url: string }; + likes: number; + comments?: number; + acronym: string; +} + +const PostBox = ({ author, date, location, text, likes, image, id, comments, acronym }: PostProps) => { + const { t } = useTranslation('feed'); + const [imageLoaded, setImageLoaded] = useState(false); + const [openComments, setOpenComments] = useState(false); + + return ( + + + + {acronym} + + + {author} + + {date} • {location} + + + + {text} + + {image?.photo_url && !imageLoaded && ( +
+
+
+ )} + {image?.photo_url && ( + posted setImageLoaded(true)} + /> + )} + + + + + success + {likes} + + { + if (!comments) return; + setOpenComments((prev) => !prev); + }} + className={`flex gap-2 px-2 py-1 rounded-md items-center ${comments ? 'cursor-pointer hover:bg-neutral-100' : 'cursor-default'}`} + > + success + + {comments} {t('post.comment')} + + + + + success + {t('post.delete')} + + + {openComments && } + + ); +}; + +export default PostBox; diff --git a/src/components/RiskChart.tsx b/src/components/RiskChart.tsx new file mode 100644 index 0000000..9d852cf --- /dev/null +++ b/src/components/RiskChart.tsx @@ -0,0 +1,48 @@ +import { Box } from '@mui/material'; +import useAxios from 'axios-hooks'; +import { useTranslation } from 'react-i18next'; +import { ErrorResponse } from 'react-router-dom'; +import useUser from '@/hooks/useUser'; +import { BaseObject } from '@/schemas'; +import Loader from '@/themed/loader/Loader'; +import { ProgressBar } from '@/themed/progress-bar/ProgressBar'; +import Title from '@/themed/title/Title'; + +interface HouseReport { + greenQuantity: number; + houseQuantity: number; + orangeQuantity: number; + redQuantity: number; + siteVariationPercentage: number; + visitQuantity: number; + visitVariationPercentage: number; +} + +const RiskChart = () => { + const { t } = useTranslation('feed'); + const user = useUser(); + + const [{ data, loading }] = useAxios({ + url: `reports/house_status`, + }); + + const label = `${(user?.team as BaseObject)?.name}: ${t('riskChart.title')}`; + + return ( + + + <Box className="flex flex-col mt-6"> + {loading && <Loader />} + {!loading && ( + <> + <ProgressBar label={t('riskChart.greenSites')} progress={data?.greenQuantity || 0} color="green-600" /> + <ProgressBar label={t('riskChart.yellowSites')} progress={data?.orangeQuantity || 0} color="yellow-600" /> + <ProgressBar label={t('riskChart.redSites')} progress={data?.redQuantity || 0} color="red-600" /> + </> + )} + </Box> + </Box> + ); +}; + +export default RiskChart; diff --git a/src/components/SitesReport.tsx b/src/components/SitesReport.tsx new file mode 100644 index 0000000..c8799d5 --- /dev/null +++ b/src/components/SitesReport.tsx @@ -0,0 +1,22 @@ +import { Box } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { ProgressBar } from '@/themed/progress-bar/ProgressBar'; +import Title from '@/themed/title/Title'; + +const SitesReport = () => { + const { t } = useTranslation('feed'); + + return ( + <Box className="border-solid border-neutral-100 rounded-md p-6 mb-4"> + <Title label={t('sitesReport.title')} type="subsection" className="mb-0" /> + <Box className="flex flex-col mt-6"> + <> + <ProgressBar label={t('sitesReport.title')} progress={60} color="green-600" /> + <ProgressBar label={t('sitesReport.quantity')} progress={80} color="green-800" /> + </> + </Box> + </Box> + ); +}; + +export default SitesReport; diff --git a/src/components/list/RankViewBox.tsx b/src/components/list/RankViewBox.tsx new file mode 100644 index 0000000..02e83fe --- /dev/null +++ b/src/components/list/RankViewBox.tsx @@ -0,0 +1,81 @@ +import { Box, Tab, Tabs } from '@mui/material'; +import useAxios from 'axios-hooks'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import Text from '@/themed/text/Text'; +import Loader from '@/themed/loader/Loader'; + +function a11yProps(index: number) { + return { + id: `simple-tab-${index}`, + 'aria-controls': `simple-tabpanel-${index}`, + }; +} + +enum RankView { + VisitRank = 'visitRank', + GreenHouseRank = 'greenHouseRank', +} + +interface Rank { + first_name: string; + last_name: string; + quantity: number; +} + +interface BrigadistPerformance { + visitRank: Rank[]; + greenHouseRank: Rank[]; +} + +const RankViewBox = () => { + const { t } = useTranslation('feed'); + const [rankView, setRankView] = useState(RankView.VisitRank); + + const handleChange = (_: React.SyntheticEvent, newValue: number) => { + setRankView(Object.values(RankView)[newValue] as RankView); + }; + + const [{ data, loading, error }] = useAxios<BrigadistPerformance>({ + url: `/reports/brigadists_performance`, + }); + + const value = Object.values(RankView).indexOf(rankView); + + return ( + <Box className="border-solid border-neutral-100 rounded-md"> + <Tabs value={value} onChange={handleChange} aria-label="basic tabs example"> + <Tab label={t('rankView.users')} {...a11yProps(0)} className="w-1/2" /> + <Tab label={t('rankView.greenHouses')} {...a11yProps(1)} className="w-1/2" /> + </Tabs> + <Box className="flex flex-col p-6"> + {error && <p>Error</p>} + {loading && <Loader />} + {!loading && ( + <> + <Box className="flex justify-between mb-4"> + <Text className="text-neutral-400 opacity-60">{t('rankView.ranking')}</Text> + <Text className="text-neutral-400 opacity-60">{t('rankView.visits')}</Text> + </Box> + <Box> + {data && + data[rankView].map((item) => ( + <Box className="flex justify-between mb-6" key={item.first_name + item.quantity}> + <Box className="flex flex-row items-center gap-3"> + <Box className="rounded-full h-10 w-10 bg-neutral-100" /> + <Text className="font-semibold text-neutral-400 opacity-70 mb-0"> + {item.first_name} {item.last_name} + </Text> + </Box> + <Text className="font-semibold text-green-800 mb-0">{item.quantity}</Text> + </Box> + ))} + </Box> + </> + )} + </Box> + </Box> + ); +}; + +export default RankViewBox; diff --git a/src/i18n/locales/en/myCity.json b/src/i18n/locales/en/feed.json similarity index 75% rename from src/i18n/locales/en/myCity.json rename to src/i18n/locales/en/feed.json index 761f86c..e72e4b0 100644 --- a/src/i18n/locales/en/myCity.json +++ b/src/i18n/locales/en/feed.json @@ -1,5 +1,10 @@ { - "title": "City of", + "community": "My community's progress", + "city": "City of", + "sitesReport": { + "title": "Tariki Sites", + "quantity": "Amount of green containers" + }, "layout": { "filters": "Filters", "filter": { diff --git a/src/i18n/locales/es/myCity.json b/src/i18n/locales/es/feed.json similarity index 76% rename from src/i18n/locales/es/myCity.json rename to src/i18n/locales/es/feed.json index a5da8a6..4bd0dbb 100644 --- a/src/i18n/locales/es/myCity.json +++ b/src/i18n/locales/es/feed.json @@ -1,5 +1,10 @@ { - "title": "Ciudad de", + "community": "El progreso de mi comunidad", + "city": "Ciudad de", + "sitesReport": { + "title": "Sitios Tariki", + "quantity": "Cantidad de contenedores verdes" + }, "layout": { "filters": "Filtros", "filter": { diff --git a/src/i18n/locales/pt/myCity.json b/src/i18n/locales/pt/feed.json similarity index 75% rename from src/i18n/locales/pt/myCity.json rename to src/i18n/locales/pt/feed.json index 1c03630..ba7c765 100644 --- a/src/i18n/locales/pt/myCity.json +++ b/src/i18n/locales/pt/feed.json @@ -1,5 +1,10 @@ { - "title": "Cidade de", + "community": "O progresso da minha comunidade", + "city": "Cidade de", + "sitesReport": { + "title": "Locais Tariki", + "quantity": "Quantidade de contêineres verdes" + }, "layout": { "filters": "Filtros", "filter": { diff --git a/src/layout/AppBar.tsx b/src/layout/AppBar.tsx index 62b3393..4226458 100644 --- a/src/layout/AppBar.tsx +++ b/src/layout/AppBar.tsx @@ -59,6 +59,7 @@ const ADMIN_SPECIAL_PLACES = '/admin/special-places'; const ADMIN_TEAMS = '/admin/teams'; const MY_CITY = '/my-city'; +const MY_COMMUNITY = '/my-community'; export function AppBar({ auth = false, signUp = false, logout }: AppBarProps) { const { t } = useTranslation('translation'); @@ -126,7 +127,7 @@ export function AppBar({ auth = false, signUp = false, logout }: AppBarProps) { </ListItemIcon> <ListItemText primary={<Text type="menuItem">{t('menu.myCity')}</Text>} /> </ListItemButton> - <ListItemButton> + <ListItemButton component={Link} to={MY_COMMUNITY} selected={pathname.includes(MY_COMMUNITY)}> <ListItemIcon> <Icon type="Community" /> </ListItemIcon> diff --git a/src/pages/my-city/MyCityPage.tsx b/src/pages/my-city/MyCityPage.tsx index 664e907..c4de288 100644 --- a/src/pages/my-city/MyCityPage.tsx +++ b/src/pages/my-city/MyCityPage.tsx @@ -1,298 +1,22 @@ +import { Box, Divider, FormControl, InputLabel, MenuItem, Select, SelectChangeEvent } from '@mui/material'; +import { deserialize } from 'jsonapi-fractal'; +import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Box, Divider, FormControl, InputLabel, MenuItem, Select, SelectChangeEvent, Tab, Tabs } from '@mui/material'; -import useAxios from 'axios-hooks'; -import { deserialize, DocumentObject } from 'jsonapi-fractal'; -import React, { useEffect, useState } from 'react'; import InfiniteScroll from 'react-infinite-scroll-component'; -import { ErrorResponse } from 'react-router-dom'; import { authApi } from '@/api/axios'; -import Comment from '@/assets/icons/comment.svg'; -import ThumbsUp from '@/assets/icons/thumbs-up.svg'; -import Trash from '@/assets/icons/trash.svg'; +import PostBox from '@/components/PostBox'; +import RiskChart from '@/components/RiskChart'; +import RankViewBox from '@/components/list/RankViewBox'; import useUser from '@/hooks/useUser'; import { BaseObject } from '@/schemas'; import { Post, Team } from '@/schemas/entities'; import Loader from '@/themed/loader/Loader'; -import { ProgressBar } from '@/themed/progress-bar/ProgressBar'; -import Text from '@/themed/text/Text'; import Title from '@/themed/title/Title'; -import { formatDateFromString } from '@/util'; -import useLangContext from '@/hooks/useLangContext'; - -function a11yProps(index: number) { - return { - id: `simple-tab-${index}`, - 'aria-controls': `simple-tabpanel-${index}`, - }; -} - -interface CommentBoxProps { - postId: number; -} - -interface IComment { - content: string; - createdAt: string; - id: number; - likesCount: number; - photos?: string; - postId: number; - updatedAt: string; - canDeleteByUser: boolean; - canEditByUser: boolean; - createdBy: IPostUser; -} - -interface IPostUser { - accountId: number; - lastName: string; - userName: string; -} - -const CommentBox = ({ postId }: CommentBoxProps) => { - const [{ data, loading, error }] = useAxios({ - url: `/posts/${postId}`, - }); - const [comments, setComments] = useState<DocumentObject[]>([]); - const langContext = useLangContext(); - - useEffect(() => { - if (!data) return; - const deserializedData = deserialize<{ comments: DocumentObject[] }>(data) as { comments: DocumentObject[] }[]; - const first = deserializedData.shift(); - if (first) setComments(first.comments); - }, [data]); - - return ( - <> - {loading && <Loader height="15vh" />} - {!loading && - comments.map((commentData) => { - const comment = deserialize<IComment>(commentData) as IComment; - const acronym = `${comment.createdBy.userName[0]}${comment.createdBy.lastName[0]}`; - - return ( - <Box> - <Box className="flex items-center gap-4 mt-6"> - <Box className="min-w-20 min-h-20 -tracking-4 bg-green-100 rounded-full flex items-center justify-center"> - <Text className="mb-0 text-green-800 font-bold -tracking-4 uppercase">{acronym}</Text> - </Box> - <Box> - <Box className="flex gap-2"> - <Text className="mb-0 font-semibold"> - {comment.createdBy.userName} {comment.createdBy.lastName} - </Text> - <Text className="mb-0 opacity-60 max-w-max"> - {formatDateFromString(langContext.state.selected, comment.createdAt)} - </Text> - </Box> - <Text className="mb-0">{comment.content}</Text> - <Box className="flex gap-2 rounded-md items-center"> - <img className="self-center" src={ThumbsUp} alt="success" /> - <Text className="mb-0 font-medium opacity-60">{comment.likesCount}</Text> - </Box> - </Box> - </Box> - {error && <p>Something wrong happened</p>} - </Box> - ); - })} - </> - ); -}; - -interface PostProps { - id: number; - author: string; - date: number; - location: string; - text: string; - image?: { photo_url: string }; - likes: number; - comments?: number; - acronym: string; -} - -const PostBox = ({ author, date, location, text, likes, image, id, comments, acronym }: PostProps) => { - const { t } = useTranslation('myCity'); - const [imageLoaded, setImageLoaded] = useState(false); - const [openComments, setOpenComments] = useState(false); - - return ( - <Box key={id} className="flex-col border-solid border-neutral-100 rounded-md p-8 flex justify-between mb-4"> - <Box className="flex items-center gap-4 mb-6"> - <Box className="w-20 h-20 -tracking-4 bg-green-100 rounded-full flex items-center justify-center"> - <Text className="mb-0 text-green-800 font-bold -tracking-4 uppercase">{acronym}</Text> - </Box> - <Box> - <Text className="mb-0 font-semibold">{author}</Text> - <Text className="mb-0 opacity-60"> - {date} • {location} - </Text> - </Box> - </Box> - <Text className={`mb-0 ${image?.photo_url && 'mb-4'}`}>{text}</Text> - <Box className="rounded-lg mb-4 overflow-hidden"> - {image?.photo_url && !imageLoaded && ( - <div role="status" className="animate-pulse w-full"> - <div className="h-96 w-full bg-lightGray opacity-50 rounded-lg" /> - </div> - )} - {image?.photo_url && ( - <img - src={image?.photo_url} - className="w-full object-cover" - alt="posted" - onLoad={() => setImageLoaded(true)} - /> - )} - </Box> - <Box className="flex justify-between gap-4"> - <Box className="flex gap-6"> - <Box className="flex gap-2 px-2 py-1 rounded-md items-center cursor-default"> - <img className="self-center" src={ThumbsUp} alt="success" /> - <Text className="mb-0 font-medium opacity-60">{likes}</Text> - </Box> - <Box - onClick={() => { - if (!comments) return; - setOpenComments((prev) => !prev); - }} - className={`flex gap-2 px-2 py-1 rounded-md items-center ${comments ? 'cursor-pointer hover:bg-neutral-100' : 'cursor-default'}`} - > - <img className="self-center" src={Comment} alt="success" /> - <Text className="mb-0 font-medium opacity-60"> - {comments} {t('post.comment')} - </Text> - </Box> - </Box> - <Box className="flex gap-2 px-2 py-1 rounded-md items-center cursor-default"> - <img className="self-center" src={Trash} alt="success" /> - <Text className="mb-0 font-medium opacity-60">{t('post.delete')}</Text> - </Box> - </Box> - {openComments && <CommentBox postId={id} />} - </Box> - ); -}; - -// type PostState = Record< -// string, -// { -// post: Post; -// commentsCount: number; -// likedByUser: boolean; -// likesCount: number; -// loadingLike: boolean; -// } -// >; - -enum RankView { - VisitRank = 'visitRank', - GreenHouseRank = 'greenHouseRank', -} - -interface Rank { - first_name: string; - last_name: string; - quantity: number; -} - -interface BrigadistPerformance { - visitRank: Rank[]; - greenHouseRank: Rank[]; -} - -const RankViewBox = () => { - const { t } = useTranslation('myCity'); - const [rankView, setRankView] = useState(RankView.VisitRank); - - const handleChange = (_: React.SyntheticEvent, newValue: number) => { - setRankView(Object.values(RankView)[newValue] as RankView); - }; - - const [{ data, loading, error }] = useAxios<BrigadistPerformance>({ - url: `/reports/brigadists_performance`, - }); - - const value = Object.values(RankView).indexOf(rankView); - - return ( - <Box className="border-solid border-neutral-100 rounded-md"> - <Tabs value={value} onChange={handleChange} aria-label="basic tabs example"> - <Tab label={t('rankView.users')} {...a11yProps(0)} className="w-1/2" /> - <Tab label={t('rankView.greenHouses')} {...a11yProps(1)} className="w-1/2" /> - </Tabs> - <Box className="flex flex-col p-6"> - {error && <p>Error</p>} - {loading && <Loader />} - {!loading && ( - <> - <Box className="flex justify-between mb-4"> - <Text className="text-neutral-400 opacity-60">{t('rankView.ranking')}</Text> - <Text className="text-neutral-400 opacity-60">{t('rankView.visits')}</Text> - </Box> - <Box> - {data && - data[rankView].map((item) => ( - <Box className="flex justify-between mb-6" key={item.first_name + item.quantity}> - <Box className="flex flex-row items-center gap-3"> - <Box className="rounded-full h-10 w-10 bg-neutral-100" /> - <Text className="font-semibold text-neutral-400 opacity-70 mb-0"> - {item.first_name} {item.last_name} - </Text> - </Box> - <Text className="font-semibold text-green-800 mb-0">{item.quantity}</Text> - </Box> - ))} - </Box> - </> - )} - </Box> - </Box> - ); -}; -interface HouseReport { - greenQuantity: number; - houseQuantity: number; - orangeQuantity: number; - redQuantity: number; - siteVariationPercentage: number; - visitQuantity: number; - visitVariationPercentage: number; -} - -const RiskChart = () => { - const { t } = useTranslation('myCity'); - const user = useUser(); - - const [{ data, loading }] = useAxios<HouseReport, null, ErrorResponse>({ - url: `reports/house_status`, - }); - - const label = `${(user?.team as BaseObject)?.name}: ${t('riskChart.title')}`; - - return ( - <Box className="border-solid border-neutral-100 rounded-md p-6 mb-4"> - <Title label={label} type="subsection" className="mb-0" /> - <Box className="flex flex-col mt-6"> - {loading && <Loader />} - {!loading && ( - <> - <ProgressBar label={t('riskChart.greenSites')} progress={data?.greenQuantity || 0} color="green-600" /> - <ProgressBar label={t('riskChart.yellowSites')} progress={data?.orangeQuantity || 0} color="yellow-600" /> - <ProgressBar label={t('riskChart.redSites')} progress={data?.redQuantity || 0} color="red-600" /> - </> - )} - </Box> - </Box> - ); -}; type Sort = 'asc' | 'desc'; const MyCity = () => { - const { t } = useTranslation(['myCity', 'errorCodes']); + const { t } = useTranslation(['feed', 'errorCodes']); const [error, setError] = useState(''); const [currentPage, setCurrentPage] = useState<number>(1); const [dataList, setDataList] = useState<Post[]>([]); @@ -302,8 +26,6 @@ const MyCity = () => { const user = useUser(); const [sortFilter, setSortFilter] = useState<Sort>('desc'); const [loading, setLoading] = useState(false); - // const [userRank, setUserRank] = useState(); - // const [greenHouseRank, setGreenHouseRank] = useState(); const fetchData = async (page: number, teamId?: number | string, sort?: Sort) => { setError(''); @@ -326,33 +48,11 @@ const MyCity = () => { const deserializedData = deserialize<Post>(data); if (!deserializedData || !Array.isArray(deserializedData)) return; - // console.log( - // `rows of PAGE ${page} with TEAM ID ${teamId} >>>>`, - // deserializedData.map( - // (post, index) => `${post.id}-${post.createdBy}-${post.postText}\n`, - // ), - // ); - if (page === 1) { const uniqueList = Array.from(new Set(deserializedData.map((item) => item.id))) .map((id) => deserializedData.find((item) => item.id === id)) .filter((item) => item !== undefined); - // setState((prevState) => { - // const newState = { ...prevState }; - // uniqueList.forEach((post) => { - // newState[`${post.id}`] = { - // post, - // commentsCount: post.commentsCount || 0, - // likedByUser: post.likedByUser ?? false, - // likesCount: post.likesCount ?? 0, - // loadingLike: false, - // }; - // }); - - // return newState; - // }); - setDataList(uniqueList); } else { setDataList((prevData) => { @@ -362,21 +62,6 @@ const MyCity = () => { .map((id) => updatedList.find((item) => item.id === id)) .filter((item) => item !== undefined); - // setState((prevState) => { - // const newState = { ...prevState }; - // uniqueList.forEach((post) => { - // newState[`${post.id}`] = { - // post, - // commentsCount: post.commentsCount || 0, - // likedByUser: post.likedByUser ?? false, - // likesCount: post.likesCount ?? 0, - // loadingLike: false, - // }; - // }); - - // return newState; - // }); - return uniqueList; }); } @@ -394,7 +79,6 @@ const MyCity = () => { const loadMoreData = () => { if (!loadingMore && hasMore && !error) { - console.log('hi'); setLoadingMore(true); const nextPage = currentPage + 1; console.log('loadMoreData>>>> ', nextPage); @@ -424,19 +108,9 @@ const MyCity = () => { setLoading(false); }; - // const loadMoreData = () => { - // if (!loadingMore && hasMore && !error) { - // setLoadingMore(true); - // const nextPage = currentPage + 1; - // console.log('loadMoreData>>>> ', nextPage); - // setCurrentPage(nextPage); - // fetchData(nextPage, all ? undefined : (meData?.userProfile?.team as Team)?.id); - // } - // }; - return ( <> - <Title type="page2" label={`${t('title')} ${(user?.city as BaseObject)?.name}`} /> + <Title type="page2" label={`${t('city')} ${(user?.city as BaseObject)?.name}`} /> <Divider /> <Box className="flex pt-6 gap-6"> <Box className="w-2/3 bg-gray-300 h-full"> diff --git a/src/pages/my-community/MyCommunityPage.tsx b/src/pages/my-community/MyCommunityPage.tsx new file mode 100644 index 0000000..79265d5 --- /dev/null +++ b/src/pages/my-community/MyCommunityPage.tsx @@ -0,0 +1,132 @@ +import { Box, Divider } from '@mui/material'; +import { deserialize } from 'jsonapi-fractal'; +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import { authApi } from '@/api/axios'; +import PostBox from '@/components/PostBox'; +import SitesReport from '@/components/SitesReport'; +import useUser from '@/hooks/useUser'; +import { Post, Team } from '@/schemas/entities'; +import Loader from '@/themed/loader/Loader'; +import Title from '@/themed/title/Title'; + +type Sort = 'asc' | 'desc'; + +const MyCommunity = () => { + const { t } = useTranslation(['feed', 'errorCodes']); + const [error, setError] = useState(''); + const [currentPage, setCurrentPage] = useState<number>(1); + const [dataList, setDataList] = useState<Post[]>([]); + const [hasMore, setHasMore] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + const [all] = useState<boolean>(true); + const user = useUser(); + + const fetchData = async (page: number, teamId?: number | string, sort?: Sort) => { + setError(''); + try { + const response = await authApi.get('posts', { + headers: { + source: 'mobile', + }, + params: { + 'filter[team_id]': teamId, + 'page[number]': page, + 'page[size]': 6, + sort: 'created_at', + order: sort, + }, + }); + + const data = response?.data; + if (data) { + const deserializedData = deserialize<Post>(data); + if (!deserializedData || !Array.isArray(deserializedData)) return; + + if (page === 1) { + const uniqueList = Array.from(new Set(deserializedData.map((item) => item.id))) + .map((id) => deserializedData.find((item) => item.id === id)) + .filter((item) => item !== undefined); + + setDataList(uniqueList); + } else { + setDataList((prevData) => { + const updatedList = [...prevData, ...deserializedData]; + + const uniqueList = Array.from(new Set(updatedList.map((item) => item.id))) + .map((id) => updatedList.find((item) => item.id === id)) + .filter((item) => item !== undefined); + + return uniqueList; + }); + } + + // console.log("data.links>>>", data.links); + setHasMore(data.links?.self !== data.links?.last); + } + } catch (err) { + console.log('error>>>>>>', err); + setError(t('errorCodes:generic')); + } finally { + setLoadingMore(false); + } + }; + + const loadMoreData = () => { + if (!loadingMore && hasMore && !error) { + setLoadingMore(true); + const nextPage = currentPage + 1; + console.log('loadMoreData>>>> ', nextPage); + setCurrentPage(nextPage); + fetchData(nextPage, all ? undefined : (user?.team as Team)?.id); + } + }; + + const firstLoad = () => { + // setAll(true); + setDataList([]); + setHasMore(true); + setCurrentPage(1); + fetchData(1, undefined); + }; + + useEffect(() => { + firstLoad(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + <> + <Title type="page2" label={t('community')} /> + <Divider /> + <Box className="flex pt-6 gap-6"> + <Box className="bg-gray-300 h-full w-full"> + <SitesReport /> + <InfiniteScroll + loader={<Loader />} + hasMore={hasMore} + dataLength={dataList.length} + next={() => loadMoreData()} + > + {dataList.map((post) => ( + <PostBox + id={post.id} + author={`${post.createByUser.userName} ${post.createByUser.lastName}`} + acronym={`${post.createByUser.userName[0]}${post.createByUser.lastName[0]}`} + text={post.postText} + location={post.location} + likes={post.likesCount} + date={post.createdAt} + image={post.photoUrl} + comments={post.commentsCount} + /> + ))} + </InfiniteScroll> + </Box> + </Box> + </> + ); +}; + +export default MyCommunity; diff --git a/src/routes/AppRouter.tsx b/src/routes/AppRouter.tsx index bfa8ae0..6f069b3 100644 --- a/src/routes/AppRouter.tsx +++ b/src/routes/AppRouter.tsx @@ -27,6 +27,7 @@ import LoadUser from '../pages/loader/LoadUser'; import Loader from '../themed/loader/Loader'; import ProtectedRoute from './ProtectedRoute'; import AppHome from '@/pages/AppHome'; +import MyCommunity from '@/pages/my-community/MyCommunityPage'; // Create a React Query client const queryClient = new QueryClient({ @@ -53,6 +54,10 @@ const router = createBrowserRouter([ path: 'my-city', element: <MyCity />, }, + { + path: 'my-community', + element: <MyCommunity />, + }, ], errorElement: <RouterErrorPage />, }, diff --git a/src/types/resources.ts b/src/types/resources.ts index 1507ab1..48c6624 100644 --- a/src/types/resources.ts +++ b/src/types/resources.ts @@ -6,7 +6,7 @@ import validation from '../i18n/locales/en/validation.json'; import splash from '../i18n/locales/en/splash.json'; import permissions from '../i18n/locales/en/permissions.json'; import admin from '../i18n/locales/en/admin.json'; -import myCity from '../i18n/locales/en/myCity.json'; +import feed from '../i18n/locales/en/feed.json'; const resources = { auth, @@ -17,7 +17,7 @@ const resources = { splash, permissions, admin, - myCity, + feed, } as const; export default resources;