diff --git a/static/css/style.css b/static/css/style.css index 407b6f1..e304f05 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,21 +1,45 @@ -/* Modern Color Palette & Reset */ +/* Modern Color Palette & Reset (UI/UX Pro Max: SaaS Analytics + Glassmorphism) */ :root { - --primary-color: #6366f1; - /* Indigo */ - --secondary-color: #8b5cf6; - /* Violet */ - --accent-color: #ec4899; - /* Pink */ - --bg-color: #f8fafc; - --text-primary: #1e293b; - --text-secondary: #64748b; - --card-bg: rgba(255, 255, 255, 0.9); - --glass-border: 1px solid rgba(255, 255, 255, 0.4); + /* Primary: Trust & Intelligence (Deep Blue/Indigo) */ + --primary-color: #4F46E5; + /* Indigo-600 */ + --primary-light: #818CF8; + + /* Secondary: Vibrant Accent (Purple/Pink for graphs/highlights) */ + --secondary-color: #D946EF; + /* Fuchsia-500 */ + + /* Backgrounds */ + --bg-color: #F8FAFC; + /* Slate-50 */ + --bg-gradient-1: #E0E7FF; + /* Indigo-100 */ + --bg-gradient-2: #FCE7F3; + /* Pink-100 */ + + /* Glassmorphism Variables */ + --glass-bg: rgba(255, 255, 255, 0.75); + --glass-border: 1px solid rgba(255, 255, 255, 0.6); + --glass-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.07); + --blur-strength: 16px; + + /* Typography */ + --font-heading: 'Poppins', sans-serif; + --font-body: 'Open Sans', sans-serif; + + /* Text Colors */ + --text-primary: #1E293B; + /* Slate-800 */ + --text-secondary: #64748B; + /* Slate-500 */ + --text-tertiary: #94A3B8; + + /* Spacing & Radius */ + --radius-lg: 24px; + --radius-md: 16px; + --radius-sm: 8px; + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); - --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --radius-lg: 16px; - --radius-md: 12px; } * { @@ -26,56 +50,74 @@ } body { - font-family: 'Inter', system-ui, -apple-system, sans-serif; + font-family: var(--font-body); background: var(--bg-color); + /* Dynamic Mesh Gradient Background */ background-image: - radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.15) 0px, transparent 50%), - radial-gradient(at 100% 100%, rgba(236, 72, 153, 0.15) 0px, transparent 50%); + radial-gradient(at 0% 0%, var(--bg-gradient-1) 0px, transparent 50%), + radial-gradient(at 100% 0%, var(--bg-gradient-2) 0px, transparent 50%), + radial-gradient(at 100% 100%, #EDF2F7 0px, transparent 50%); background-attachment: fixed; color: var(--text-primary); line-height: 1.5; min-height: 100vh; + font-size: 14px; + /* Slightly smaller base for compactness */ } /* Glassmorphism Utilities */ .glass-card { - background: var(--card-bg); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); + background: var(--glass-bg); + backdrop-filter: blur(var(--blur-strength)); + -webkit-backdrop-filter: blur(var(--blur-strength)); border: var(--glass-border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); + border-radius: var(--radius-md); + box-shadow: var(--glass-shadow); + transition: transform 0.2s ease, box-shadow 0.2s ease; } -/* Layout */ +.glass-card:hover { + box-shadow: 0 12px 40px 0 rgba(31, 38, 135, 0.12); +} + +/* ... Layout ... */ .app-container { - max-width: 1200px; + max-width: 1000px; + /* More compact container */ margin: 0 auto; - padding: 20px; + padding: 24px 16px; } /* Header */ .main-header { text-align: center; - padding: 40px 0 30px; + padding: 32px 0 24px; } .logo { display: inline-flex; align-items: center; gap: 12px; - color: var(--primary-color); margin-bottom: 8px; } .logo i { font-size: 28px; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; } .logo h1 { - font-size: 28px; - font-weight: 800; - letter-spacing: -0.5px; + font-family: var(--font-heading); + font-size: 26px; + font-weight: 700; + letter-spacing: -0.02em; + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; } .subtitle { @@ -123,22 +165,34 @@ body { font-weight: 600; font-size: 14px; border-radius: 50px; - padding: 10px 20px; - transition: all 0.2s; + padding: 10px 24px; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); display: inline-flex; align-items: center; gap: 8px; + position: relative; + overflow: hidden; +} + +.btn:active { + transform: scale(0.96); } .btn-lg { - padding: 12px 28px; - font-size: 15px; + padding: 14px 32px; + font-size: 16px; } .btn-primary { background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); color: white; - box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3); + box-shadow: 0 4px 15px rgba(79, 70, 229, 0.4); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.btn-primary:hover { + box-shadow: 0 8px 25px rgba(79, 70, 229, 0.5); + transform: translateY(-2px); } .btn-primary:active { @@ -216,14 +270,23 @@ body { } .stat-card { - background: white; - padding: 20px; + background: rgba(255, 255, 255, 0.65); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + padding: 24px; border-radius: var(--radius-md); box-shadow: var(--shadow-sm); display: flex; align-items: center; - gap: 16px; - border: 1px solid #f1f5f9; + gap: 20px; + border: 1px solid rgba(255, 255, 255, 0.5); + transition: transform 0.2s; +} + +.stat-card:hover { + transform: translateY(-2px); + background: rgba(255, 255, 255, 0.85); + box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.05); } .icon-circle { @@ -343,18 +406,29 @@ body { /* Daily List */ .day-card { - margin-bottom: 16px; - border: 1px solid rgba(255, 255, 255, 0.5); + margin-bottom: 20px; + border: var(--glass-border); + border-radius: var(--radius-md); overflow: hidden; + background: rgba(255, 255, 255, 0.4); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.02); + transition: all 0.3s ease; +} + +.day-card:hover { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05); } .day-header { - padding: 16px 20px; + padding: 16px 24px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; - background: rgba(255, 255, 255, 0.4); + background: rgba(255, 255, 255, 0.3); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .day-info { diff --git a/static/js/main.js b/static/js/main.js index d14721e..ca64f72 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -153,25 +153,45 @@ function displayData(data) { function renderStats(data) { let totalQty = 0; let totalAmt = 0; - const days = data.daily_summary.length; - const uniqueProducts = new Set(); // Simplified unique product count logic based on daily summary data structure might differ, here we approximate or iterate raw if needed. - // Correction: Use raw_data for unique products count if available, or iterate all dailies. + + // 1. Calculate Unique Days (based on YYYY-MM-DD) + const uniqueDates = new Set(); + + // 2. Track Unique Products + const uniqueProducts = new Set(); data.daily_summary.forEach(day => { - if (day.summary_info) { + // Extract YYYY-MM-DD from timestamp "YYYY-MM-DD HH:MM:SS" + if (day.date && day.date.length >= 10) { + uniqueDates.add(day.date.substring(0, 10)); + } + + // Sum up totals + // Priority: Use the sum of products (day.total_...) if available. + // If products are missing/zero but header info (summary_info) exists, use that. + if (day.products && day.products.length > 0) { + // Use calculated sum from products + totalQty += day.total_quantity; + totalAmt += day.total_amount; + } else if (day.summary_info) { + // Fallback to header info if no products parsed totalQty += day.summary_info.total_quantity; totalAmt += day.summary_info.total_amount; } else { totalQty += day.total_quantity; totalAmt += day.total_amount; } - day.products.forEach(p => uniqueProducts.add(p.product)); + + // Collect unique products + if (day.products) { + day.products.forEach(p => uniqueProducts.add(p.product)); + } }); // Count Animation animateValue(elements.totalAmount, totalAmt, '¥'); animateValue(elements.totalQuantity, totalQty, ''); - animateValue(elements.totalDays, days, ''); + animateValue(elements.totalDays, uniqueDates.size, ''); // Use unique dates count animateValue(elements.totalProducts, uniqueProducts.size, ''); } @@ -205,8 +225,19 @@ function animateValue(obj, end, prefix = '') { window.requestAnimationFrame(step); } +// Pagination State +const ITEMS_PER_PAGE = 20; +let displayedItems = ITEMS_PER_PAGE; + function renderDailyList(data) { - elements.dailyData.innerHTML = data.daily_summary.map(day => { + const listContainer = elements.dailyData; + const totalItems = data.daily_summary.length; + + // Slice data based on current limit + const visibleData = data.daily_summary.slice(0, displayedItems); + + // Render list + const listHTML = visibleData.map(day => { const totalAmt = day.summary_info ? day.summary_info.total_amount : day.total_amount; const totalQty = day.summary_info ? day.summary_info.total_quantity : day.total_quantity; @@ -236,6 +267,29 @@ function renderDailyList(data) { `; }).join(''); + + // Append "Show More" button if needed + let buttonHTML = ''; + if (displayedItems < totalItems) { + buttonHTML = ` +