Files
orion/app/templates/vendor/analytics.html
Samir Boulahtit 646d789af7 feat: add vendor notifications and analytics pages (Phase 3)
New pages:
- Notifications center: view, mark read, delete, settings modal
- Analytics: period selector, stats overview, feature-gated advanced metrics

Changes:
- Add routes for /vendor/{code}/notifications and /analytics
- Create notifications.js and analytics.js with full functionality
- Create notifications.html and analytics.html templates
- Update sidebar with Analytics link and Notifications in Customers section
- Update vendor-frontend-parity-plan.md to mark Phase 3 complete

Vendor frontend now at ~95% parity with admin.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-01 16:35:52 +01:00

232 lines
12 KiB
HTML

{# app/templates/vendor/analytics.html #}
{% extends "vendor/base.html" %}
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
{% block title %}Analytics{% endblock %}
{% block alpine_data %}vendorAnalytics(){% endblock %}
{% block content %}
<!-- Page Header -->
{% call page_header_flex(title='Analytics', subtitle='Track your business performance') %}
<div class="flex items-center gap-4">
<!-- Period Selector -->
<select
x-model="period"
@change="changePeriod(period)"
class="px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300"
>
<template x-for="option in periodOptions" :key="option.value">
<option :value="option.value" x-text="option.label"></option>
</template>
</select>
{{ refresh_button(loading_var='loading', onclick='loadAllData()', variant='secondary') }}
</div>
{% endcall %}
{{ loading_state('Loading analytics...') }}
{{ error_state('Error loading analytics') }}
<!-- Analytics Content -->
<div x-show="!loading && !error" class="space-y-8">
<!-- Overview Stats -->
<div class="grid gap-6 md:grid-cols-2 xl:grid-cols-4">
<!-- Products -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
<span x-html="$icon('cube', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Total Products</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(dashboardStats.total_products)">0</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
<span x-text="formatNumber(dashboardStats.active_products)"></span> active
(<span x-text="activeProductPercent"></span>%)
</p>
</div>
</div>
<!-- Orders -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
<span x-html="$icon('shopping-cart', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Total Orders</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(dashboardStats.total_orders)">0</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
<span x-text="formatNumber(dashboardStats.pending_orders)"></span> pending
</p>
</div>
</div>
<!-- Customers -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
<span x-html="$icon('users', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Customers</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(dashboardStats.total_customers)">0</p>
<p class="text-xs text-gray-500 dark:text-gray-400">Total registered</p>
</div>
</div>
<!-- Inventory -->
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="p-3 mr-4 rounded-full"
:class="{
'text-green-500 bg-green-100 dark:text-green-100 dark:bg-green-500': stockHealth.color === 'green',
'text-yellow-500 bg-yellow-100 dark:text-yellow-100 dark:bg-yellow-500': stockHealth.color === 'yellow',
'text-red-500 bg-red-100 dark:text-red-100 dark:bg-red-500': stockHealth.color === 'red'
}">
<span x-html="$icon('archive', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Inventory</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(dashboardStats.total_inventory)">0</p>
<p class="text-xs" :class="{
'text-green-600 dark:text-green-400': stockHealth.color === 'green',
'text-yellow-600 dark:text-yellow-400': stockHealth.color === 'yellow',
'text-red-600 dark:text-red-400': stockHealth.color === 'red'
}">
<span x-text="dashboardStats.low_stock_count"></span> low stock items
</p>
</div>
</div>
</div>
<!-- Period Analytics (if available) -->
<template x-if="analytics">
<div class="grid gap-6 md:grid-cols-3">
<!-- Imports This Period -->
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">Imports</h3>
<span class="text-sm text-gray-500 dark:text-gray-400" x-text="getPeriodLabel()"></span>
</div>
<div class="flex items-center">
<div class="p-3 mr-4 text-teal-500 bg-teal-100 rounded-full dark:text-teal-100 dark:bg-teal-500">
<span x-html="$icon('cloud-download', 'w-6 h-6')"></span>
</div>
<div>
<p class="text-2xl font-bold text-gray-700 dark:text-gray-200" x-text="formatNumber(analytics.imports?.count || 0)"></p>
<p class="text-sm text-gray-500 dark:text-gray-400">import jobs</p>
</div>
</div>
</div>
<!-- Products Added This Period -->
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">Products Added</h3>
<span class="text-sm text-gray-500 dark:text-gray-400" x-text="getPeriodLabel()"></span>
</div>
<div class="flex items-center">
<div class="p-3 mr-4 text-indigo-500 bg-indigo-100 rounded-full dark:text-indigo-100 dark:bg-indigo-500">
<span x-html="$icon('plus-circle', 'w-6 h-6')"></span>
</div>
<div>
<p class="text-2xl font-bold text-gray-700 dark:text-gray-200" x-text="formatNumber(analytics.catalog?.products_added || 0)"></p>
<p class="text-sm text-gray-500 dark:text-gray-400">new products</p>
</div>
</div>
</div>
<!-- Inventory Locations -->
<div class="p-6 bg-white rounded-lg shadow-xs dark:bg-gray-800">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">Locations</h3>
<span class="text-sm text-gray-500 dark:text-gray-400">Total</span>
</div>
<div class="flex items-center">
<div class="p-3 mr-4 text-orange-500 bg-orange-100 rounded-full dark:text-orange-100 dark:bg-orange-500">
<span x-html="$icon('location-marker', 'w-6 h-6')"></span>
</div>
<div>
<p class="text-2xl font-bold text-gray-700 dark:text-gray-200" x-text="formatNumber(analytics.inventory?.total_locations || 0)"></p>
<p class="text-sm text-gray-500 dark:text-gray-400">inventory locations</p>
</div>
</div>
</div>
</div>
</template>
<!-- Feature Upgrade Prompt (if analytics not available) -->
<template x-if="!analytics">
<div class="p-8 bg-white rounded-lg shadow-xs dark:bg-gray-800 text-center">
<div class="mx-auto w-16 h-16 mb-4 flex items-center justify-center bg-purple-100 dark:bg-purple-900 rounded-full">
<span x-html="$icon('chart-bar', 'w-8 h-8 text-purple-600 dark:text-purple-400')"></span>
</div>
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200 mb-2">Advanced Analytics</h3>
<p class="text-gray-500 dark:text-gray-400 mb-4">
Upgrade your plan to access detailed analytics including import trends, product performance, and more.
</p>
<a href="#" @click.prevent="$dispatch('navigate', '/vendor/' + vendorCode + '/billing')"
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
<span x-html="$icon('sparkles', 'w-4 h-4 mr-2')"></span>
View Plans
</a>
</div>
</template>
<!-- Quick Stats Summary -->
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 overflow-hidden">
<div class="p-4 border-b dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-800 dark:text-gray-200">Quick Summary</h3>
</div>
<div class="p-4">
<div class="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
<!-- Active Products Rate -->
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">Active Product Rate</p>
<div class="flex items-center">
<p class="text-xl font-bold text-gray-700 dark:text-gray-200" x-text="activeProductPercent + '%'"></p>
<span class="ml-2 text-green-500" x-show="activeProductPercent >= 80">
<span x-html="$icon('trending-up', 'w-4 h-4')"></span>
</span>
</div>
</div>
<!-- Featured Products -->
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">Featured Products</p>
<p class="text-xl font-bold text-gray-700 dark:text-gray-200" x-text="formatNumber(dashboardStats.featured_products)"></p>
</div>
<!-- Pending Orders -->
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">Pending Orders</p>
<div class="flex items-center">
<p class="text-xl font-bold text-gray-700 dark:text-gray-200" x-text="formatNumber(dashboardStats.pending_orders)"></p>
<span class="ml-2 text-yellow-500" x-show="dashboardStats.pending_orders > 0">
<span x-html="$icon('clock', 'w-4 h-4')"></span>
</span>
</div>
</div>
<!-- Stock Health -->
<div class="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
<p class="text-sm text-gray-500 dark:text-gray-400 mb-1">Stock Health</p>
<div class="flex items-center">
<span class="px-2 py-1 text-sm font-medium rounded-full"
:class="{
'text-green-700 bg-green-100 dark:bg-green-900 dark:text-green-300': stockHealth.color === 'green',
'text-yellow-700 bg-yellow-100 dark:bg-yellow-900 dark:text-yellow-300': stockHealth.color === 'yellow',
'text-red-700 bg-red-100 dark:bg-red-900 dark:text-red-300': stockHealth.color === 'red'
}"
x-text="stockHealth.label"></span>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script src="{{ url_for('static', path='vendor/js/analytics.js') }}"></script>
{% endblock %}