Refactor: Modernize Activity page UI to match site design

Update page structure:
- Rename wrapper class to 'activity' (matches AdminPage pattern)
- Update h1 to activity__title with consistent styling
- Organize content with BEM naming convention

Redesign controls:
- Replace basic input with styled input-wrapper component
- Add input-suffix "days" label inside input container
- Custom number input styling with hover/focus states
- Better ToggleButton integration with control-label
- Responsive flex layout with proper mobile handling

Enhance chart presentation:
- Wrap each graph in chart-card component
- Add background, borders, and rounded corners
- Clear chart-card__title headers
- Fixed height charts (35vh desktop, 30vh mobile)
- Minimum height prevents squashing (300px/250px)
- Consistent spacing and padding

Improve top content section:
- Grid layout for top content items (3 cols → 1 col mobile)
- Card-based items with borders and hover effects
- Hover: border highlight + translateY animation
- Better visual hierarchy with section-title

Styling details:
- Use CSS variables (--background-ui, --text-color-50, etc.)
- Match AdminPage typography (2rem title, 300 weight)
- Consistent border-radius (12px cards, 8px inputs)
- Proper mobile-only responsive breakpoints
- Remove old commented-out code

Result: Professional, cohesive design matching rest of site 
This commit is contained in:
2026-02-27 18:16:38 +01:00
parent e0ce0ea6da
commit 0c4c30d1a0

View File

@@ -1,6 +1,6 @@
<template> <template>
<div v-if="plexUserId && plexUsername" class="wrapper"> <div v-if="plexUserId && plexUsername" class="activity">
<h1>Your watch activity</h1> <h1 class="activity__title">Watch Activity</h1>
<!-- Stats Overview --> <!-- Stats Overview -->
<div v-if="watchStats" class="stats-overview"> <div v-if="watchStats" class="stats-overview">
@@ -22,34 +22,36 @@
</div> </div>
</div> </div>
<div style="display: flex; flex-direction: row"> <div class="controls">
<label class="filter" for="dayinput"> <div class="control-group">
<span>Days:</span> <label class="control-label">Time Range</label>
<div class="input-wrapper">
<input <input
id="dayinput" v-model.number="days"
v-model="days" class="days-input"
class="dayinput"
placeholder="days"
type="number" type="number"
pattern="[0-9]*" min="1"
max="365"
@change="fetchChartData" @change="fetchChartData"
/> />
</label> <span class="input-suffix">days</span>
</div>
</div>
<div class="filter"> <div class="control-group">
<span>Data sorted by:</span> <label class="control-label">View Mode</label>
<toggle-button <toggle-button
v-model:selected="graphViewMode" v-model:selected="graphViewMode"
class="filter-item"
:options="[GraphTypes.Plays, GraphTypes.Duration]" :options="[GraphTypes.Plays, GraphTypes.Duration]"
@change="fetchChartData" @change="fetchChartData"
/> />
</div> </div>
</div> </div>
<div class="chart-section"> <div class="activity__charts">
<h3 class="chart-header">Activity per day:</h3> <div class="chart-card">
<div class="graph"> <h3 class="chart-card__title">Daily Activity</h3>
<div class="chart-card__graph">
<Graph <Graph
v-if="playsByDayData" v-if="playsByDayData"
:data="playsByDayData" :data="playsByDayData"
@@ -60,9 +62,11 @@
:graph-value-type="selectedGraphViewMode.valueType" :graph-value-type="selectedGraphViewMode.valueType"
/> />
</div> </div>
</div>
<h3 class="chart-header">Activity by media type:</h3> <div class="chart-card">
<div class="graph"> <h3 class="chart-card__title">Activity by Media Type</h3>
<div class="chart-card__graph">
<Graph <Graph
v-if="playsByDayofweekData" v-if="playsByDayofweekData"
:data="playsByDayofweekData" :data="playsByDayofweekData"
@@ -73,9 +77,11 @@
:graph-value-type="GraphValueTypes.Number" :graph-value-type="GraphValueTypes.Number"
/> />
</div> </div>
</div>
<h3 class="chart-header">Watch time by hour:</h3> <div class="chart-card">
<div class="graph"> <h3 class="chart-card__title">Viewing Patterns by Hour</h3>
<div class="chart-card__graph">
<Graph <Graph
v-if="hourlyData" v-if="hourlyData"
:data="hourlyData" :data="hourlyData"
@@ -87,10 +93,11 @@
/> />
</div> </div>
</div> </div>
</div>
<!-- Top Content --> <!-- Top Content -->
<div v-if="topContent.length > 0" class="top-content-section"> <div v-if="topContent.length > 0" class="activity__top-content">
<h3 class="chart-header">Most watched:</h3> <h3 class="section-title">Most Watched</h3>
<div class="top-content-list"> <div class="top-content-list">
<div <div
v-for="(item, index) in topContent" v-for="(item, index) in topContent"
@@ -262,38 +269,41 @@
<style lang="scss" scoped> <style lang="scss" scoped>
@import "scss/variables"; @import "scss/variables";
@import "scss/media-queries";
.wrapper { .activity {
padding: 2rem; padding: 3rem;
max-width: 100%;
@include mobile-only { @include mobile-only {
padding: 0 0.8rem; padding: 0.75rem;
}
} }
.filter { &__title {
margin-top: 0.5rem; margin: 0 0 2rem 0;
display: inline-flex; font-size: 2rem;
flex-direction: column;
font-size: 1.2rem;
&:not(:first-of-type) {
margin-left: 1.25rem;
}
input {
width: 100%;
font-size: inherit;
max-width: 6rem;
background-color: $background-ui;
color: $text-color;
}
span {
font-size: inherit;
line-height: 1;
margin: 0.5rem 0;
font-weight: 300; font-weight: 300;
color: $text-color;
line-height: 1;
@include mobile-only {
font-size: 1.5rem;
margin: 0 0 1rem 0;
}
}
&__charts {
display: flex;
flex-direction: column;
gap: 1.5rem;
@include mobile-only {
gap: 1rem;
}
}
&__top-content {
margin-top: 2rem;
} }
} }
@@ -322,27 +332,121 @@
// } // }
// } // }
.chart-section { .chart-card {
display: flex; background: var(--background-ui);
flex-wrap: wrap; border-radius: 12px;
padding: 1.5rem;
border: 1px solid var(--text-color-50);
.graph { @include mobile-only {
padding: 1rem;
}
&__title {
margin: 0 0 1rem 0;
font-size: 1.2rem;
font-weight: 500;
color: $text-color;
@include mobile-only {
font-size: 1rem;
}
}
&__graph {
position: relative; position: relative;
height: 35vh; height: 35vh;
width: 90vw; min-height: 300px;
margin-bottom: 2rem;
@include mobile-only {
height: 30vh;
min-height: 250px;
}
}
} }
.chart-header { .section-title {
font-weight: 300; margin: 0 0 1rem 0;
font-size: 1.2rem;
font-weight: 500;
color: $text-color;
} }
.controls {
display: flex;
gap: 2rem;
margin-bottom: 2rem;
flex-wrap: wrap;
@include mobile-only {
flex-direction: column;
gap: 1rem;
}
}
.control-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
min-width: 200px;
@include mobile-only {
min-width: 0;
width: 100%;
}
}
.control-label {
font-size: 0.9rem;
font-weight: 500;
color: var(--text-color-60);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.input-wrapper {
display: flex;
align-items: center;
background: var(--background-ui);
border: 1px solid var(--text-color-50);
border-radius: 8px;
overflow: hidden;
transition: border-color 0.2s;
&:hover,
&:focus-within {
border-color: var(--text-color);
}
}
.days-input {
flex: 1;
background: transparent;
border: none;
padding: 0.75rem 1rem;
font-size: 1rem;
color: $text-color;
outline: none;
width: 80px;
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
opacity: 1;
}
}
.input-suffix {
padding: 0 1rem;
font-size: 0.9rem;
color: var(--text-color-60);
user-select: none;
} }
.stats-overview { .stats-overview {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1.5rem; gap: 1.5rem;
margin: 2rem 0; margin-bottom: 2rem;
@include mobile-only { @include mobile-only {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
@@ -389,14 +493,14 @@
} }
} }
.top-content-section {
margin-top: 3rem;
}
.top-content-list { .top-content-list {
display: flex; display: grid;
flex-direction: column; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 0.75rem; gap: 1rem;
@include mobile-only {
grid-template-columns: 1fr;
}
} }
.top-content-item { .top-content-item {
@@ -406,10 +510,12 @@
background: var(--background-ui); background: var(--background-ui);
padding: 1rem; padding: 1rem;
border-radius: 8px; border-radius: 8px;
transition: background 0.2s; border: 1px solid var(--text-color-50);
transition: all 0.2s;
&:hover { &:hover {
background: var(--background-40); border-color: var(--text-color);
transform: translateY(-2px);
} }
} }