feat(unified-mobile-material-design): Complete US-111 - CacheStatsView Mobile Material Design

Implemented by Ralph autonomous loop.
Iteration: 1

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-12 11:10:08 +00:00
parent 3cb95e1183
commit 25af73dae2
3 changed files with 635 additions and 23 deletions

View File

@@ -1,6 +1,44 @@
<template>
<div class="cache-stats-view">
<div class="stats-header">
<div class="cache-stats-view" :class="{ 'mobile-layout': isMobile }">
<!-- US-111: Mobile Material Design Top Bar -->
<MobileTopBar
v-if="isMobile"
title="Statistici Cache"
:show-menu="true"
:actions="mobileTopBarActions"
@menu-click="toggleMobileMenu"
@action-click="handleTopBarAction"
/>
<!-- US-111: Mobile Hamburger Menu -->
<Sidebar v-if="isMobile" v-model:visible="mobileMenuVisible" position="left" class="mobile-sidebar">
<template #header>
<div class="sidebar-header">
<span class="sidebar-title">ROA2WEB</span>
</div>
</template>
<div class="sidebar-menu">
<router-link to="/data-entry" class="sidebar-item">
<i class="pi pi-receipt"></i>
<span>Bonuri</span>
</router-link>
<router-link to="/reports/cache-stats" class="sidebar-item active">
<i class="pi pi-database"></i>
<span>Statistici Cache</span>
</router-link>
<router-link to="/reports/dashboard" class="sidebar-item">
<i class="pi pi-chart-bar"></i>
<span>Rapoarte</span>
</router-link>
<router-link to="/data-entry/ocr-metrics" class="sidebar-item">
<i class="pi pi-cog"></i>
<span>Setări</span>
</router-link>
</div>
</Sidebar>
<!-- Desktop Header -->
<div class="stats-header" v-if="!isMobile">
<h1>Cache Statistics</h1>
<div class="actions">
<Button
@@ -178,11 +216,14 @@
/>
</template>
</Dialog>
<!-- US-111: Mobile Bottom Navigation -->
<MobileBottomNav v-if="isMobile" :items="mobileBottomNavItems" />
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
import { ref, computed, onMounted, onUnmounted } from "vue";
import { useCacheStore } from "@reports/stores/cacheStore";
import { useCompanyStore } from "@reports/stores/sharedStores";
import { useToast } from "primevue/usetoast";
@@ -196,6 +237,11 @@ import InputSwitch from "primevue/inputswitch";
import Dialog from "primevue/dialog";
import RadioButton from "primevue/radiobutton";
import Message from "primevue/message";
import Sidebar from "primevue/sidebar";
// US-111: Mobile Material Design components
import MobileTopBar from "@shared/components/mobile/MobileTopBar.vue";
import MobileBottomNav from "@shared/components/mobile/MobileBottomNav.vue";
const cacheStore = useCacheStore();
const companyStore = useCompanyStore();
@@ -209,6 +255,49 @@ const userCacheEnabled = ref(true);
const showClearDialog = ref(false);
const clearScope = ref("current");
// US-111: Mobile state
const isMobile = ref(window.innerWidth < 768);
const mobileMenuVisible = ref(false);
// US-111: Toggle mobile hamburger menu
const toggleMobileMenu = () => {
mobileMenuVisible.value = !mobileMenuVisible.value;
};
// US-111: Mobile TopBar actions (refresh only for cache stats)
const mobileTopBarActions = computed(() => [
{
icon: "pi pi-refresh",
label: "Actualizează",
tooltip: "Actualizează statistici",
},
]);
// US-111: Handle top bar action clicks
const handleTopBarAction = (action) => {
if (action.icon === "pi pi-refresh") {
loadStats();
}
};
// US-111: Bottom nav items for MobileBottomNav component
const mobileBottomNavItems = computed(() => [
{ to: "/data-entry", icon: "pi pi-receipt", label: "Bonuri" },
{
to: "/reports/cache-stats",
icon: "pi pi-database",
label: "Cache",
active: true,
},
{ to: "/reports/dashboard", icon: "pi pi-chart-bar", label: "Rapoarte" },
{ to: "/data-entry/ocr-metrics", icon: "pi pi-cog", label: "Setări" },
]);
// US-111: Handle window resize
const handleResize = () => {
isMobile.value = window.innerWidth < 768;
};
const responseTimesTable = computed(() => {
if (!stats.value?.response_times) return [];
@@ -314,14 +403,21 @@ function clearError() {
}
onMounted(() => {
// US-111: Add resize listener for mobile detection
window.addEventListener("resize", handleResize);
loadStats();
});
onUnmounted(() => {
// US-111: Remove resize listener
window.removeEventListener("resize", handleResize);
});
</script>
<style scoped>
/* Container - Uses global .app-container pattern */
.cache-stats-view {
padding: 2rem;
padding: var(--space-xl);
max-width: 1400px;
margin: 0 auto;
}
@@ -331,7 +427,7 @@ onMounted(() => {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
margin-bottom: var(--space-xl);
}
.stats-header h1 {
@@ -341,29 +437,29 @@ onMounted(() => {
.actions {
display: flex;
gap: 0.5rem;
gap: var(--space-sm);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 1.5rem;
gap: var(--space-lg);
}
.status-content {
display: flex;
flex-direction: column;
gap: 1rem;
gap: var(--space-md);
}
.status-item {
display: flex;
align-items: center;
gap: 1rem;
gap: var(--space-md);
}
.status-item label {
font-weight: 600;
font-weight: var(--font-semibold);
min-width: 140px;
}
@@ -373,14 +469,14 @@ onMounted(() => {
}
.hit-rate h3 {
margin: 0 0 0.5rem 0;
margin: 0 0 var(--space-sm) 0;
color: var(--primary-color);
}
.hit-rate p {
margin: 0 0 1rem 0;
margin: 0 0 var(--space-md) 0;
color: var(--text-color-secondary);
font-size: 0.9rem;
font-size: var(--text-sm);
}
.queries-list {
@@ -390,7 +486,7 @@ onMounted(() => {
}
.queries-list li {
padding: 0.5rem 0;
padding: var(--space-sm) 0;
border-bottom: 1px solid var(--surface-border);
}
@@ -399,8 +495,8 @@ onMounted(() => {
}
.average-row {
margin-top: 1rem;
padding-top: 1rem;
margin-top: var(--space-md);
padding-top: var(--space-md);
border-top: 2px solid var(--surface-border);
text-align: center;
}
@@ -412,7 +508,7 @@ onMounted(() => {
}
.details-list li {
padding: 0.5rem 0;
padding: var(--space-sm) 0;
display: flex;
justify-content: space-between;
}
@@ -420,26 +516,100 @@ onMounted(() => {
.clear-options {
display: flex;
flex-direction: column;
gap: 1rem;
margin-top: 1rem;
gap: var(--space-md);
margin-top: var(--space-md);
}
.p-field-radiobutton {
display: flex;
align-items: center;
gap: 0.5rem;
gap: var(--space-sm);
}
.response-times-card {
grid-column: 1 / -1;
}
/* ================================================
US-111: Mobile Layout Styles
================================================ */
.mobile-layout {
padding: 0;
padding-top: 56px; /* MobileTopBar height */
padding-bottom: 56px; /* MobileBottomNav height */
}
.mobile-layout .stats-grid {
padding: var(--space-sm);
gap: var(--space-sm);
grid-template-columns: 1fr;
}
/* Mobile sidebar styles */
.mobile-sidebar .sidebar-header {
padding: var(--space-md);
border-bottom: 1px solid var(--surface-border);
}
.mobile-sidebar .sidebar-title {
font-size: var(--text-lg);
font-weight: var(--font-bold);
color: var(--color-primary);
}
.mobile-sidebar .sidebar-menu {
padding: var(--space-sm) 0;
}
.mobile-sidebar .sidebar-item {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-md) var(--space-lg);
color: var(--text-color);
text-decoration: none;
transition: background-color var(--transition-fast);
}
.mobile-sidebar .sidebar-item:hover,
.mobile-sidebar .sidebar-item:active {
background: var(--surface-hover);
}
.mobile-sidebar .sidebar-item.active {
background: var(--blue-50);
color: var(--color-primary);
}
.mobile-sidebar .sidebar-item i {
font-size: var(--text-xl);
width: 24px;
text-align: center;
}
/* ================================================
Dark Mode Support for Mobile
================================================ */
[data-theme="dark"] .mobile-sidebar .sidebar-item.active {
background: var(--blue-900);
color: var(--blue-400);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .mobile-sidebar .sidebar-item.active {
background: var(--blue-900);
color: var(--blue-400);
}
}
/* Responsive - Cache stats specific adjustments */
@media (max-width: 768px) {
.stats-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
gap: var(--space-md);
}
.stats-grid {