diff --git a/package.json b/package.json index 06825b9..809b091 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@mui/x-charts": "^6.18.4", "construct-style-sheets-polyfill": "3.1.0", "date-fns": "^3.0.6", + "moment": "^2.30.1", "react": "18.2.0", "react-dom": "18.2.0", "react-json-tree": "^0.18.0", diff --git a/src/pages/sidepanel/SidePanel.tsx b/src/pages/sidepanel/SidePanel.tsx index ab65cab..8349d9a 100644 --- a/src/pages/sidepanel/SidePanel.tsx +++ b/src/pages/sidepanel/SidePanel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { Route, Routes, useNavigate, useLocation } from 'react-router-dom'; import BottomNavigation from '@root/src/pages/sidepanel/components/BottomNavigation'; @@ -220,6 +220,7 @@ const SidePanel: React.FC = ({ key, isEnabled }) => { profile={currentProfile} getter={profileTab} setter={setProfileTab} + tagConfig={tagConfig} /> } /> diff --git a/src/pages/sidepanel/sections/Profile.tsx b/src/pages/sidepanel/sections/Profile.tsx index f354103..f5341dc 100644 --- a/src/pages/sidepanel/sections/Profile.tsx +++ b/src/pages/sidepanel/sections/Profile.tsx @@ -3,15 +3,17 @@ import { Box, Stack, Tab, Tabs, CircularProgress } from '@mui/material'; import CustomTabPanel from '@root/src/pages/sidepanel/components/CustomTabPanel'; import ProfileDetail from '@root/src/pages/sidepanel/sections/ProfileDetail'; import ProfileSummary from '@root/src/pages/sidepanel/sections/ProfileSummary'; +import { TagConfigModel } from '@root/src/shared/models/tagConfigModel'; interface ProfileTabProps { profile: any; profileIsLoading: boolean; getter: number; setter: Dispatch>; + tagConfig: TagConfigModel; } -const Profile: React.FC = ({ profileIsLoading, profile, getter, setter }) => { +const Profile: React.FC = ({ profileIsLoading, profile, getter, setter, tagConfig }) => { const handleSetTab = (event, newValue) => { setter(newValue); }; @@ -31,7 +33,7 @@ const Profile: React.FC = ({ profileIsLoading, profile, getter, ) : ( - + )} diff --git a/src/pages/sidepanel/sections/ProfileSummary.tsx b/src/pages/sidepanel/sections/ProfileSummary.tsx index d776b86..ebbb2f3 100644 --- a/src/pages/sidepanel/sections/ProfileSummary.tsx +++ b/src/pages/sidepanel/sections/ProfileSummary.tsx @@ -1,8 +1,25 @@ -import React, { useEffect, useState } from 'react'; -import { Box, Button, Chip, Divider, LinearProgress, Stack, Typography } from '@mui/material'; +import React, { useCallback, useEffect, useState } from 'react'; +import { + Box, + Button, + Chip, + Divider, + LinearProgress, + Stack, + Typography, + Grid, + Table, + TableContainer, + TableBody, + TableRow, + TableCell, + Collapse, + Tooltip, +} from '@mui/material'; import { makeStyles } from '@mui/styles'; -import { Lock } from '@mui/icons-material'; +import { ExpandMore, HelpOutline, Lock } from '@mui/icons-material'; import SimpleTable from '@root/src/pages/sidepanel/components/SimpleTable'; +import { TagConfigModel } from '@root/src/shared/models/tagConfigModel'; interface BarStylesProps { backgroundGradient: string; @@ -19,6 +36,23 @@ const barStyles = makeStyles(() => ({ })); interface ProfileSummaryTabProps { profile: any; + tagConfig: TagConfigModel; +} + +export interface ContentEntity { + created?: string; + fetched?: string; + updated?: string; + description?: string; + longDescription?: string; + httpstatus?: string; + language?: string; + primaryImage?: string; + url?: string; + collections?: string[]; + // topics are a map[string]number + topics?: Record; + confidence?: number; } const HighlightBox: React.FC<{ headline: string; cta?: React.ReactNode; value: React.ReactNode }> = ({ @@ -92,7 +126,86 @@ const CustomBarChart: React.FC = ({ data, color1, color2 }: ); }; -const ProfileSummary: React.FC = ({ profile }) => { +const RecommendationTile = ({ item }: { item: ContentEntity }) => { + const [open, setOpen] = useState(false); + + return ( + + setOpen(!open)}> + + + {item?.url} + + + + + + + + + + + URL + + + + {item?.url} + + + + + + Description + + + + {item?.description || item?.longDescription || 'No description available'} + + + + + + Topics + + + {item.topics && + Object.keys(item.topics).map((topic, index) => ( + + ))} + + + + + Image + + + {'Not + + + + + + + Confidence + + + + + + + + {Math.round(item.confidence * 100) || 0} + + + +
+
+
+
+ ); +}; + +const ProfileSummary: React.FC = ({ profile, tagConfig }) => { const [hasContent, setHasContent] = useState(false); const [hasScores, setHasScores] = useState(false); const [totalAttributes, setTotalAttributes] = useState(0); @@ -100,6 +213,43 @@ const ProfileSummary: React.FC = ({ profile }) => { const [affinities, setAffinities] = useState<{ [key: string]: number }>({}); const [computedAttributes, setComputedAttributes] = useState<{ [key: string]: string }>({}); + const [recommendations, setRecommendations] = useState([]); + const recommendAPI = useCallback(async () => { + if (!profile || !tagConfig || !profile.data) { + return; + } + const accountID = tagConfig?.cid?.[0]; + const uid = profile?.data?._uid as string; + if (!accountID || !uid) { + return; + } + + const response = await fetch(`https://api.lytics.io/api/content/recommend/${accountID}/user/_uid/${uid}`); + const json = await response.json(); + // for each item in the data array in JSON, convert to a ContentEntity + const contentEntities = json.data.map(item => { + const entity: ContentEntity = { + created: item.created, + fetched: item.fetched, + updated: item.updated, + description: item.description, + longDescription: item.long_description, + httpstatus: item.httpstatus, + primaryImage: item.primary_image, + url: item?.url, + topics: item?.global, + confidence: item.confidence, + }; + return entity; + }); + setRecommendations(contentEntities); + return; + }, [profile, tagConfig]); + + useEffect(() => { + recommendAPI(); + }, [profile, tagConfig, recommendAPI]); + const appendScore = (scoresArray, profileData, propertyName, label) => { const propertyValue = profileData?.user?.[propertyName]; @@ -301,6 +451,17 @@ const ProfileSummary: React.FC = ({ profile }) => { ), }, + { + label: 'Content Recommendations', + position: 'top', + fancyValue: ( + <> + {recommendations.map((item, index) => ( + + ))} + + ), + }, ]} /> diff --git a/yarn.lock b/yarn.lock index 14e3e23..752eb57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5058,6 +5058,11 @@ mlly@^1.2.0, mlly@^1.4.2: pkg-types "^1.0.3" ufo "^1.3.0" +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + ms@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"