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
TopMedia.js
144 lines
| 1 | import { useState } from "@wordpress/element"; |
| 2 | const { __ } = wp.i18n; |
| 3 | import { Button, Badge, Text } from "@bsf/force-ui"; |
| 4 | import { ArrowRight } from "lucide-react"; |
| 5 | import { useHistory } from "../router/router"; |
| 6 | import iconWhiteSvg from "../../../../img/icon-white.svg"; |
| 7 | import ChartEmptyState from "./ChartEmptyState"; |
| 8 | import TruncatedTitle from "./TruncatedTitle"; |
| 9 | import RankedTable from "./RankedTable"; |
| 10 | |
| 11 | const MediaThumbnail = ({ item }) => { |
| 12 | const [imageError, setImageError] = useState(false); |
| 13 | |
| 14 | return ( |
| 15 | <div className="flex items-center gap-3 min-w-0"> |
| 16 | {item?.poster_image && !imageError ? ( |
| 17 | <img |
| 18 | src={item.poster_image} |
| 19 | alt={item?.title || ""} |
| 20 | className="flex-shrink-0 object-cover w-[75px] h-auto aspect-video rounded-[2px]" |
| 21 | loading="lazy" |
| 22 | onError={() => setImageError(true)} |
| 23 | /> |
| 24 | ) : ( |
| 25 | <div className="flex-shrink-0 flex items-center justify-center w-[75px] aspect-video box-border bg-[#d1d5db] rounded-[2px]"> |
| 26 | <img |
| 27 | src={iconWhiteSvg} |
| 28 | alt="" |
| 29 | width="20" |
| 30 | height="auto" |
| 31 | className="h-auto" |
| 32 | /> |
| 33 | </div> |
| 34 | )} |
| 35 | <TruncatedTitle title={item.title} className="flex-1" /> |
| 36 | </div> |
| 37 | ); |
| 38 | }; |
| 39 | |
| 40 | /** |
| 41 | * Top Media table with server-side pagination. |
| 42 | * |
| 43 | * @param {Object} props |
| 44 | * @param {Array} props.media Current page of media items. |
| 45 | * @param {number} props.currentPage 1-indexed page. |
| 46 | * @param {number} props.totalItems Total items across pages. |
| 47 | * @param {number} props.totalPages Total page count. |
| 48 | * @param {number} props.perPage Items per page. |
| 49 | * @param {Function} props.onPageChange (page) => void. |
| 50 | * @param {string} [props.fromUserId] Optional user id for back-navigation. |
| 51 | */ |
| 52 | const TopMedia = ({ |
| 53 | media = [], |
| 54 | currentPage = 1, |
| 55 | totalItems = 0, |
| 56 | totalPages = 0, |
| 57 | perPage = 10, |
| 58 | onPageChange, |
| 59 | fromUserId, |
| 60 | }) => { |
| 61 | const history = useHistory(); |
| 62 | |
| 63 | const goToDetail = (item) => |
| 64 | history.push({ |
| 65 | tab: "Analytics", |
| 66 | detail: "media", |
| 67 | id: String(item.id), |
| 68 | ...(fromUserId ? { from_user: String(fromUserId) } : {}), |
| 69 | }); |
| 70 | |
| 71 | const columns = [ |
| 72 | { |
| 73 | key: "title", |
| 74 | header: __("Media", "presto-player"), |
| 75 | headerClassName: "text-text-secondary font-medium py-2", |
| 76 | cellClassName: "py-3", |
| 77 | render: (item) => <MediaThumbnail item={item} />, |
| 78 | }, |
| 79 | { |
| 80 | key: "totalViews", |
| 81 | header: __("Views", "presto-player"), |
| 82 | headerClassName: "whitespace-nowrap text-text-secondary font-medium py-2", |
| 83 | cellClassName: "whitespace-nowrap py-2", |
| 84 | render: (item) => ( |
| 85 | <Text as="span" size="sm" className="text-text-secondary"> |
| 86 | {`${item.totalViews} ${__("views", "presto-player")}`} |
| 87 | </Text> |
| 88 | ), |
| 89 | }, |
| 90 | { |
| 91 | key: "avgViewTime", |
| 92 | header: __("Avg View Time", "presto-player"), |
| 93 | headerClassName: "whitespace-nowrap text-text-secondary font-medium py-2", |
| 94 | cellClassName: "whitespace-nowrap py-2", |
| 95 | render: (item) => ( |
| 96 | <Badge |
| 97 | label={item.avgViewTime} |
| 98 | type="rounded" |
| 99 | size="sm" |
| 100 | variant="neutral" |
| 101 | className="border-0 bg-gray-100 w-fit" |
| 102 | /> |
| 103 | ), |
| 104 | }, |
| 105 | ]; |
| 106 | |
| 107 | return ( |
| 108 | <RankedTable |
| 109 | title={__("Most Watched", "presto-player")} |
| 110 | columns={columns} |
| 111 | rows={media} |
| 112 | rowKey={(item) => item.id} |
| 113 | onRowClick={goToDetail} |
| 114 | rowAction={(item) => ( |
| 115 | <Button |
| 116 | variant="ghost" |
| 117 | size="xs" |
| 118 | className="text-brand-primary font-semibold" |
| 119 | iconPosition="right" |
| 120 | icon={<ArrowRight className="size-3" />} |
| 121 | onClick={(e) => { |
| 122 | e.stopPropagation(); |
| 123 | goToDetail(item); |
| 124 | }} |
| 125 | > |
| 126 | {__("View Details", "presto-player")} |
| 127 | </Button> |
| 128 | )} |
| 129 | pagination={{ |
| 130 | currentPage, |
| 131 | totalItems: totalItems || media.length, |
| 132 | totalPages: totalPages || Math.ceil((totalItems || media.length) / perPage), |
| 133 | perPage, |
| 134 | onPageChange, |
| 135 | }} |
| 136 | emptyState={ |
| 137 | <ChartEmptyState className="min-h-[180px] pb-6 px-1" proGated /> |
| 138 | } |
| 139 | /> |
| 140 | ); |
| 141 | }; |
| 142 | |
| 143 | export default TopMedia; |
| 144 |