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>
232 lines
12 KiB
HTML
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 %}
|