feat: add Modal popup for extra User Info
This commit is contained in:
90
frontend/src/components/UserModal.tsx
Normal file
90
frontend/src/components/UserModal.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Dialog, DialogPanel, DialogTitle } from "@headlessui/react";
|
||||
import type { User } from "../types/ApiTypes";
|
||||
import StatsStyling from "../styles/stats_styling";
|
||||
|
||||
const styles = StatsStyling;
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
userData: User | null;
|
||||
username: string;
|
||||
};
|
||||
|
||||
export default function UserModal({ open, onClose, userData, username }: Props) {
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} style={{ position: "relative", zIndex: 50 }}>
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
background: "rgba(0,0,0,0.45)",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: 16,
|
||||
}}
|
||||
>
|
||||
<DialogPanel style={{ ...styles.card, width: "min(520px, 95vw)" }}>
|
||||
<div style={styles.headerBar}>
|
||||
<div>
|
||||
<DialogTitle style={styles.sectionTitle}>{username}</DialogTitle>
|
||||
<p style={styles.sectionSubtitle}>User activity breakdown</p>
|
||||
</div>
|
||||
|
||||
<button onClick={onClose} style={styles.buttonSecondary}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{!userData ? (
|
||||
<p style={styles.sectionSubtitle}>No data for this user.</p>
|
||||
) : (
|
||||
<div style={styles.topUsersList}>
|
||||
<div style={{...styles.topUserName, fontSize: 20}}>{userData.author}</div>
|
||||
<div style={styles.topUserItem}>
|
||||
<div style={styles.topUserName}>Posts</div>
|
||||
<div style={styles.topUserMeta}>{userData.post}</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.topUserItem}>
|
||||
<div style={styles.topUserName}>Comments</div>
|
||||
<div style={styles.topUserMeta}>{userData.comment}</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.topUserItem}>
|
||||
<div style={styles.topUserName}>Comment/Post Ratio</div>
|
||||
<div style={styles.topUserMeta}>
|
||||
{userData.comment_post_ratio.toFixed(2)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={styles.topUserItem}>
|
||||
<div style={styles.topUserName}>Comment Share</div>
|
||||
<div style={styles.topUserMeta}>
|
||||
{(userData.comment_share * 100).toFixed(1)}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{userData.vocab ? (
|
||||
<div style={styles.topUserItem}>
|
||||
<div style={styles.topUserName}>Vocab Richness</div>
|
||||
<div style={styles.topUserMeta}>
|
||||
{userData.vocab.vocab_richness} (avg {userData.vocab.avg_words_per_event} words/event)
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import ActivityHeatmap from "../stats/ActivityHeatmap";
|
||||
import { ReactWordcloud } from '@cp949/react-wordcloud';
|
||||
import StatsStyling from "../styles/stats_styling";
|
||||
import Card from "../components/Card";
|
||||
import UserModal from "../components/UserModal";
|
||||
|
||||
import {
|
||||
type TopUser,
|
||||
@@ -22,7 +23,8 @@ import {
|
||||
type UserAnalysisResponse,
|
||||
type TimeAnalysisResponse,
|
||||
type ContentAnalysisResponse,
|
||||
type FilterResponse
|
||||
type FilterResponse,
|
||||
type User
|
||||
} from '../types/ApiTypes'
|
||||
|
||||
const styles = StatsStyling;
|
||||
@@ -57,6 +59,9 @@ const StatPage = () => {
|
||||
const [contentData, setContentData] = useState<ContentAnalysisResponse | null>(null);
|
||||
const [summary, setSummary] = useState<SummaryResponse | null>(null);
|
||||
|
||||
const [selectedUser, setSelectedUser] = useState<string | null>(null);
|
||||
const selectedUserData: User | null = userData?.users.find((u) => u.author === selectedUser) ?? null;
|
||||
|
||||
|
||||
const searchInputRef = useRef<HTMLInputElement>(null);
|
||||
const beforeDateRef = useRef<HTMLInputElement>(null);
|
||||
@@ -275,10 +280,11 @@ return (
|
||||
<p style={styles.sectionSubtitle}>Most active authors</p>
|
||||
|
||||
<div style={styles.topUsersList}>
|
||||
{userData?.top_users.map((item) => (
|
||||
{userData?.top_users.slice(0, 100).map((item) => (
|
||||
<div
|
||||
key={`${item.author}-${item.source}`}
|
||||
style={styles.topUserItem}
|
||||
style={{ ...styles.topUserItem, cursor: "pointer" }}
|
||||
onClick={() => setSelectedUser(item.author)}
|
||||
>
|
||||
<div style={styles.topUserName}>{item.author}</div>
|
||||
<div style={styles.topUserMeta}>
|
||||
@@ -299,6 +305,13 @@ return (
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<UserModal
|
||||
open={!!selectedUser}
|
||||
onClose={() => setSelectedUser(null)}
|
||||
username={selectedUser ?? ""}
|
||||
userData={selectedUserData}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ const StatsStyling: Record<string, CSSProperties> = {
|
||||
topUserName: {
|
||||
fontWeight: 700,
|
||||
fontSize: 14,
|
||||
color: "black"
|
||||
},
|
||||
|
||||
topUserMeta: {
|
||||
|
||||
Reference in New Issue
Block a user