Emails
1 month ago
EngagementChart
1 month ago
MediaHub
1 month ago
Onboarding
1 month ago
Popup
1 month ago
Skeletons
1 week ago
WhatsNew
1 month ago
charts
1 month ago
test
1 month ago
AdminMenuSync.js
1 month ago
ChartEmptyState.js
1 month ago
ChooseDate.js
1 month ago
ColorPicker.js
1 month ago
ExtendPlugins.js
1 month ago
Filters.js
1 month ago
Link.js
1 month ago
Navbar.js
1 week ago
NoFound.js
1 month ago
PageHeader.js
1 month ago
PluginRecommendations.js
1 month ago
PostScheduleField.js
1 month ago
PrestoPlayerIcon.js
1 month ago
ProGateOverlay.js
1 month ago
QuickAccess.js
1 month ago
RankedTable.js
1 month ago
StatCard.js
1 month ago
TopMedia.js
1 month ago
TopPerformingMedia.js
1 month ago
TopUsers.js
1 month ago
TruncatedTitle.js
1 month ago
UpgradeNotice.js
1 month ago
UpgradeToPro.js
1 month ago
VideoModal.js
1 month ago
WelcomeBanner.js
1 month ago
TopPerformingMedia.js
136 lines
| 1 | import React, { useState } from "react"; |
| 2 | const { __ } = wp.i18n; |
| 3 | import { Label, Container, Select, Text } from "@bsf/force-ui"; |
| 4 | import ChartEmptyState from "./ChartEmptyState"; |
| 5 | import Link from "./Link"; |
| 6 | import TruncatedTitle from "./TruncatedTitle"; |
| 7 | import { useLocation } from "../router/router"; |
| 8 | |
| 9 | /** |
| 10 | * Top Performing widget showing top media or top users in a table. |
| 11 | * Pure presentational — data must be passed via the `data` prop. |
| 12 | */ |
| 13 | const TopPerformingMedia = ({ data }) => { |
| 14 | const location = useLocation(); |
| 15 | const fromTab = location.params?.tab || "Dashboard"; |
| 16 | |
| 17 | const topMedia = data?.topMedia || []; |
| 18 | const topUsers = data?.topUsers || []; |
| 19 | |
| 20 | const [selectedId, setSelectedId] = useState("media"); |
| 21 | |
| 22 | const options = [ |
| 23 | { |
| 24 | id: "media", |
| 25 | label: __("Most Watched", "presto-player"), |
| 26 | data: topMedia, |
| 27 | countLabel: __("Views", "presto-player"), |
| 28 | }, |
| 29 | { |
| 30 | id: "users", |
| 31 | label: __("Most Engaged Viewers", "presto-player"), |
| 32 | data: topUsers, |
| 33 | countLabel: __("Views", "presto-player"), |
| 34 | }, |
| 35 | ]; |
| 36 | |
| 37 | const selectedOption = |
| 38 | options.find((opt) => opt.id === selectedId) || options[0]; |
| 39 | |
| 40 | // Transformed data shape: media = { id, title, totalViews, avgViewTime } |
| 41 | // Transformed data shape: users = { id, name, totalViews, avgViewTime } |
| 42 | const getTitle = (item) => { |
| 43 | if (selectedId === "media") { |
| 44 | return item?.title || __("Untitled", "presto-player"); |
| 45 | } |
| 46 | return item?.name || __("Unknown User", "presto-player"); |
| 47 | }; |
| 48 | |
| 49 | return ( |
| 50 | <div className="h-auto p-4 flex flex-col gap-3 border-[0.5px] border-border-subtle border-solid shadow-sm bg-background-primary rounded-xl"> |
| 51 | {/* Header with inline Select */} |
| 52 | <div className="flex items-center justify-between gap-4"> |
| 53 | <Label className="font-semibold"> |
| 54 | {__("What's Performing", "presto-player")} |
| 55 | </Label> |
| 56 | <div className="min-w-[180px]"> |
| 57 | <Select |
| 58 | size="sm" |
| 59 | value={selectedOption} |
| 60 | onChange={(option) => setSelectedId(option.id)} |
| 61 | by="id" |
| 62 | > |
| 63 | <Select.Button label="" render={(value) => value?.label || ""} /> |
| 64 | <Select.Options> |
| 65 | {options.map((option) => ( |
| 66 | <Select.Option key={option.id} value={option}> |
| 67 | {option.label} |
| 68 | </Select.Option> |
| 69 | ))} |
| 70 | </Select.Options> |
| 71 | </Select> |
| 72 | </div> |
| 73 | </div> |
| 74 | |
| 75 | {/* Content */} |
| 76 | {selectedOption?.data?.length > 0 ? ( |
| 77 | <div className="overflow-hidden rounded-lg border border-solid border-border-subtle"> |
| 78 | {/* Header */} |
| 79 | <div className="flex items-center bg-background-secondary px-3 py-2"> |
| 80 | <span className="flex-1 text-sm font-medium text-text-secondary"> |
| 81 | {selectedId === "media" |
| 82 | ? __("Media", "presto-player") |
| 83 | : __("Viewer", "presto-player")} |
| 84 | </span> |
| 85 | <span className="w-10 text-right text-sm font-medium text-text-secondary"> |
| 86 | {selectedOption.countLabel} |
| 87 | </span> |
| 88 | </div> |
| 89 | |
| 90 | {/* Rows */} |
| 91 | {selectedOption.data.map((item, index) => { |
| 92 | const title = getTitle(item); |
| 93 | const hasLink = !!item?.id; |
| 94 | |
| 95 | return ( |
| 96 | <div |
| 97 | key={item?.id || index} |
| 98 | className="flex items-center px-3 py-3 border-t border-solid border-border-subtle border-b-0 border-x-0" |
| 99 | > |
| 100 | <div className="flex-1 min-w-0"> |
| 101 | {hasLink ? ( |
| 102 | <Link |
| 103 | params={{ |
| 104 | page: "presto-dashboard", |
| 105 | tab: "Analytics", |
| 106 | detail: selectedId === "media" ? "media" : "user", |
| 107 | id: String(item.id), |
| 108 | from: fromTab, |
| 109 | }} |
| 110 | > |
| 111 | <a className="cursor-pointer no-underline hover:no-underline block min-w-0"> |
| 112 | <TruncatedTitle title={title} /> |
| 113 | </a> |
| 114 | </Link> |
| 115 | ) : ( |
| 116 | <TruncatedTitle title={title} /> |
| 117 | )} |
| 118 | </div> |
| 119 | <div className="w-10 text-right shrink-0 whitespace-nowrap"> |
| 120 | <Text as="span" size="sm" className="text-text-secondary"> |
| 121 | {item?.totalViews || "0"} |
| 122 | </Text> |
| 123 | </div> |
| 124 | </div> |
| 125 | ); |
| 126 | })} |
| 127 | </div> |
| 128 | ) : ( |
| 129 | <ChartEmptyState className="min-h-[112px] pt-6 pb-8" proGated /> |
| 130 | )} |
| 131 | </div> |
| 132 | ); |
| 133 | }; |
| 134 | |
| 135 | export default TopPerformingMedia; |
| 136 |