Loading...

Admin Dashboard

Sign in to manage your telemedicine platform

{{ error }}

{{ allMenuItems.find(item => item.id === currentView)?.name || 'Dashboard' }}

{{ currentEnvironment }}
{{ new Date().toLocaleString() }}

⚠️ Production Environment

You are managing the PRODUCTION environment. Changes will affect live data. Please be careful!

Total Products

{{ stats.products }}

Total Doctors

{{ stats.doctors }}

Total Pharmacies

{{ stats.pharmacies }}

Total Orders

{{ stats.orders }}

Total Users

{{ stats.users }}

Quick Actions

Slack Error Logging Test

Developer Tools

Test that Slack error notifications are working correctly. Messages will be sent to #soos-errors.

{{ slackTestResult.message }}

Showing {{ filteredProducts.length }} of {{ products.length }} products
Image Name / Abbreviation THC / CBD Price / Stock Category CannaFlow Status Actions
{{ product.name }}
{{ product.abbreviation || '-' }}
THC: {{ product.syncedFields?.thc || '-' }}%
CBD: {{ product.syncedFields?.cbd || '-' }}%
€{{ (product.syncedFields?.price / 100).toFixed(2) || '0.00' }}
Stock: {{ product.syncedFields?.stock || 0 }}
{{ product.category || '-' }} {{ product.cannaflowId }} Not linked {{ product.active !== false ? 'Active' : 'Inactive' }}
Showing {{ filteredTemplates.length }} of {{ templates.length }} templates
Image Product Name Category Manufacturer Variants Status Actions
{{ template.displayName }}
{{ template.cultivar || '-' }}
{{ template.category || '-' }} {{ template.manufacturer || '-' }}
{{ template.cannaflowIds?.length || 0 }} ({{ getAvailableVariantCount(template) }} available)
{{ template.active !== false ? 'Active' : 'Inactive' }}

{{ selectedTemplate.displayName }}

{{ templateVariants.length }} variant{{ templateVariants.length !== 1 ? 's' : '' }}

{{ editingVariant ? 'Edit Variant' : 'Add New Variant' }}

= {{ (variantFormData.price / 100).toFixed(2) }} EUR

No variants found for this template

{{ variant.name }}
{{ !variant.active ? 'Inactive' : variant.availability ? 'Available' : 'Unavailable' }}
THC: {{ variant.thc }}%
CBD: {{ variant.cbd }}%
Price: {{ typeof variant.price === 'number' ? (variant.price / 100).toFixed(2) : (parseFloat(variant.price) / 100 || 0).toFixed(2) }} EUR
ID: {{ variant.cannaflowId }}
Qty Inc: {{ variant.quantityIncrement || '-' }}
Min Order: {{ variant.minOrderQuantity || '-' }}
{{ variant.country }} Irradiated
Last synced: {{ new Date(variant.lastSyncedAt).toLocaleString() }}
Name Specialty Email Phone Actions
{{ doctor.name }}
{{ doctor.specialty }} {{ doctor.email }} {{ doctor.phone }}
Name Email Phone Address Actions
{{ pharmacy.name }}
{{ pharmacy.email }} {{ pharmacy.phone }} {{ pharmacy.street }}, {{ pharmacy.city }} {{ pharmacy.zip }}

Catalog Sync

Updates prices & availability from pharmacy catalog

Last Result {{ catalogSyncResult.success ? 'Success' : 'Failed' }}

{{ catalogSyncResult.stats?.variantsUpdated || 0 }}

Updated

{{ catalogSyncResult.stats?.variantsCreated || 0 }}

Created

{{ catalogSyncResult.stats?.variantsUnavailable || 0 }}

Unavailable

Templates: {{ catalogSyncResult.stats?.templatesProcessed || 0 }} Duration: {{ catalogSyncResult.stats?.duration || 0 }}ms
⚠️ {{ catalogSyncResult.stats.newVariantsDetected }} new variants detected in registry!

Registry Sync

Scans full Cannaflow registry for new variants

Last Result {{ registrySyncResult.success ? 'Success' : 'Failed' }}

{{ registrySyncResult.stats?.registryProductsScanned || 0 }}

Products Scanned

{{ registrySyncResult.stats?.newVariantsDetected || 0 }}

New Variants Found

⚠️ Broken products skipped:

{{ registrySyncResult.stats.brokenProducts.join(', ') }}

Duration: {{ registrySyncResult.stats?.duration || 0 }}ms

Sync Status

Templates

{{ variantSyncStatus.templatesCount || 0 }}

Active Variants

{{ variantSyncStatus.activeVariantsCount || 0 }}

Last Catalog Sync

{{ variantSyncStatus.lastCatalogSync ? formatDateTime(variantSyncStatus.lastCatalogSync) : 'Never' }}

Last Registry Scan

{{ variantSyncStatus.lastRegistryScan ? formatDateTime(variantSyncStatus.lastRegistryScan) : 'Never' }}

Click refresh to load sync status

Catalog Sync

  • • Fetches products from pharmacy catalog
  • • Updates prices, availability, stock
  • • Runs automatically every 6 hours
  • • Only updates existing variants

Registry Sync

  • • Scans full Cannaflow registry
  • • Detects new variants for templates
  • • Sends email notifications
  • • Handles broken products gracefully
Filtered by User: {{ getUserDisplayName(userIdFilter) }} ({{ userIdFilter }})
Order ID Doctor User Status Items Created Invoice Actions
{{ order.id.substring(0, 8) }}...
{{ getDoctorName(order.doctorId) }} {{ getUserDisplayName(order.userId) }}
{{ getOrderStatusLabel(order.status) }} ⚠️
{{ order.items?.length || 0 }} items {{ formatDate(order.createdAt) }}
-
ID Name Email Phone Birthdate Actions
{{ user.id }}
{{ user.fullName || `${user.firstName || ''} ${user.lastName || ''}`.trim() || 'N/A' }}
{{ user.email || 'N/A' }} {{ user.phoneNumber || 'N/A' }} {{ user.birthdate || 'N/A' }}

Bulk Invoice Generation

Generate and download invoices for all orders within a specified date range.

Regular Invoices

This will generate invoices for all orders created between {{ new Date(invoiceStartDate).toLocaleDateString() }} and {{ new Date(invoiceEndDate).toLocaleDateString() }}. All invoices will be downloaded as a ZIP file.

GOA Invoices (German Customers)

This will generate GOA invoices for all orders created between {{ new Date(goaInvoiceStartDate).toLocaleDateString() }} and {{ new Date(goaInvoiceEndDate).toLocaleDateString() }}. All GOA invoices will be downloaded as a ZIP file.

Total Vouchers

{{ voucherAnalytics?.totalVouchers || 0 }}

Active Vouchers

{{ voucherAnalytics?.activeVouchers || 0 }}

Total Redemptions

{{ voucherAnalytics?.totalRedemptions || 0 }}

Total Discount Given

€{{ ((voucherAnalytics?.totalDiscountGiven || 0) / 100).toFixed(2) }}

Manage Vouchers

Code Type Value Applies To Usage Status Actions
{{ voucher.code }}
{{ voucher.description }}
{{ voucher.type }} {{ voucher.value }}% €{{ (voucher.value / 100).toFixed(2) }} {{ voucher.appliesTo }} {{ voucher.usageCount || 0 }} / {{ voucher.usageLimit }} / ∞ {{ voucher.active ? 'Active' : 'Inactive' }}

Redemption Statistics

Voucher Code Redemptions Total Discount Status
{{ stat.code }} {{ stat.redemptions }} €{{ (stat.totalDiscount / 100).toFixed(2) }} {{ stat.active ? 'Active' : 'Inactive' }}
Quick Select:
{{ calculateDaysInRange() }} days selected
Selected Period: {{ formatDisplayDate(reportDateRange.startDate) }} – {{ formatDisplayDate(reportDateRange.endDate) }} {{ reportPresets.find(p => p.id === reportDateRange.preset)?.label }}
Comparing with previous {{ calculateDaysInRange() }} days

Loading report data...

{{ reportingError }}

Total Orders

{{ reportingData.summary?.totalOrders || 0 }}

{{ getTrendIcon(reportingData.stats?.orders?.total, getPreviousStats()?.orders?.total) }} {{ getTrendValue(reportingData.stats?.orders?.total, getPreviousStats()?.orders?.total) }} vs previous period

Paid Orders

{{ reportingData.summary?.paidOrders || 0 }}

{{ getTrendIcon(reportingData.stats?.orders?.paid, getPreviousStats()?.orders?.paid) }} {{ getTrendValue(reportingData.stats?.orders?.paid, getPreviousStats()?.orders?.paid) }} vs previous period

Revenue

€{{ Math.round((reportingData.summary?.totalRevenue || 0) / 100) }}

{{ getTrendIcon(reportingData.stats?.revenue?.paid, getPreviousStats()?.revenue?.paid) }} {{ getTrendValue(reportingData.stats?.revenue?.paid, getPreviousStats()?.revenue?.paid) }} vs previous period

Total Grams

{{ reportingData.summary?.totalGrams || 0 }}g

{{ getTrendIcon(reportingData.stats?.flowers?.grams, getPreviousStats()?.flowers?.grams) }} {{ getTrendValue(reportingData.stats?.flowers?.grams, getPreviousStats()?.flowers?.grams) }} vs previous period

Active Users

{{ reportingData.summary?.activeUsers || 0 }}

{{ getTrendIcon(reportingData.stats?.users?.activeInRange || reportingData.stats?.users?.activeThisWeek, getPreviousStats()?.users?.activeInRange || getPreviousStats()?.users?.activeThisWeek) }} {{ getTrendValue(reportingData.stats?.users?.activeInRange || reportingData.stats?.users?.activeThisWeek, getPreviousStats()?.users?.activeInRange || getPreviousStats()?.users?.activeThisWeek) }} vs previous period

KPIs

{{ reportingData.summary?.totalKPIs || 0 }}

Click to view details

Top 5 Brands by Grams

Top 5 Strains by Grams

Revenue by Category

User Age Distribution

Order Status

Top 10 Pharmacies

Rank Pharmacy Orders Grams Revenue % Share
{{ index + 1 }} {{ pharmacy.name }} {{ pharmacy.orders }} {{ pharmacy.grams }}g €{{ Math.round(pharmacy.revenue / 100) }} {{ pharmacy.percentage?.toFixed(1) }}%

User Behavior Metrics

New Users {{ reportingData.stats?.users?.newThisWeek ?? reportingData.stats?.users?.newInRange ?? 0 }}
Active Users {{ reportingData.stats?.users?.activeThisWeek ?? reportingData.stats?.users?.activeInRange ?? 0 }}
Avg Orders per User {{ reportingData.stats?.users?.avgOrdersPerUser?.toFixed(2) || 0 }}
First-time Customers (placing their first order ever) {{ reportingData.stats?.users?.firstTimeCustomers ?? 0 }} ({{ reportingData.stats?.users?.firstTimeCustomersPercentage?.toFixed(1) || 0 }}%)
Returning Customers (have ordered before) {{ reportingData.stats?.users?.returningCustomers ?? 0 }} ({{ reportingData.stats?.users?.returningCustomersPercentage?.toFixed(1) || 0 }}%)
Avg Revenue per User €{{ Math.round((reportingData.stats?.users?.avgRevenuePerUser || 0) / 100) }}
Avg Grams per User {{ reportingData.stats?.users?.avgGramsPerUser?.toFixed(1) || 0 }}g

Product Categories

Flowers {{ reportingData.stats?.flowers?.orders || 0 }} different strains
{{ reportingData.stats?.flowers?.grams || 0 }}g €{{ Math.round((reportingData.stats?.flowers?.revenue || 0) / 100) }} €{{ (reportingData.stats?.flowers?.avgPricePerGram / 100)?.toFixed(2) || '0.00' }}/g
Extracts {{ reportingData.stats?.extracts?.orders || 0 }} different strains
{{ reportingData.stats?.extracts?.ml || 0 }}ml €{{ Math.round((reportingData.stats?.extracts?.revenue || 0) / 100) }} €{{ (reportingData.stats?.extracts?.avgPricePerMl / 100)?.toFixed(2) || '0.00' }}/ml
Edibles {{ reportingData.stats?.edibles?.orders || 0 }} different strains
{{ reportingData.stats?.edibles?.pieces || 0 }} pieces €{{ Math.round((reportingData.stats?.edibles?.revenue || 0) / 100) }} €{{ (reportingData.stats?.edibles?.avgPricePerPiece / 100)?.toFixed(2) || '0.00' }}/pc
Report Period: {{ reportingData.period?.start ? new Date(reportingData.period.start).toLocaleDateString() : 'N/A' }} - {{ reportingData.period?.end ? new Date(reportingData.period.end).toLocaleDateString() : 'N/A' }} | Generated: {{ reportingData.generatedAt ? new Date(reportingData.generatedAt).toLocaleString() : 'N/A' }}

No Report Data

Select a week and click Refresh to load the report.

Access Denied

You don't have the required role to access this dashboard.

Valid roles: admin, content, reporting

Contact an administrator to request access.

Register New Doctor

A random password will be generated and shown after registration

Doctor Registered Successfully!

Doctor account created successfully

Email: {{ registeredDoctorInfo.email }}

UID: {{ registeredDoctorInfo.uid }}

Important: Save this password!

This is the only time you'll see this password. Please copy it and send it to the doctor securely.

{{ registeredDoctorInfo.password }}

Change Password for {{ selectedDoctorForPassword?.firstName }} {{ selectedDoctorForPassword?.lastName }}

Choose to either generate a random password or set a custom one

OR

Minimum 8 characters

Doctor Statistics

Loading statistics...

{{ doctorStats.doctorName }}

{{ doctorStats.totalOrders }}

Total Treated

{{ doctorStats.totalInitial }}

Initial

{{ doctorStats.totalFollowUp }}

Follow-Up

Month Total Initial Follow-Up
{{ stat.month }} {{ stat.year }} {{ stat.total }} {{ stat.initial }} {{ stat.followUp }}
No orders found for this doctor

Cancel Order

Order ID:

{{ cancelOrderData.id }}

Customer:

{{ cancelOrderData.user?.email || 'N/A' }}

Created:

{{ formatDate(cancelOrderData.createdAt) }}

Warning

This action will cancel the order in CannaFlow and send a cancellation email to the customer. This action cannot be undone.

{{ (editingItem || editingTemplate) ? 'Edit' : 'Add' }} {{ currentView === 'products' ? 'Product' : currentView === 'templates' ? 'Template' : currentView === 'doctors' ? 'Doctor' : currentView === 'pharmacies' ? 'Pharmacy' : currentView === 'users' ? 'User' : currentView === 'vouchers' ? 'Voucher' : currentView.slice(0, -1) }}

Upload Product Images

Product: {{ selectedProduct.name }}

Abbreviation: {{ selectedProduct.abbreviation }}

Product Gallery ({{ selectedProduct.images.length }} image{{ selectedProduct.images.length !== 1 ? 's' : '' }})

High Low

{{ imageUrl.split('/').pop() }}


No images uploaded yet

Upload high-res and low-res images. Name files with "_h" or "_high" for high-res, "_l" or "_low" for low-res.

Selected Files ({{ imageFiles.length }})

  • {{ file.name }} ({{ (file.size / 1024).toFixed(1) }} KB)

Order Details

{{ selectedOrderDetails.id }}
{{ selectedOrderDetails.status.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) }}
{{ selectedOrderDetails.userId }}
Name: {{ selectedOrderDetails.user.name || selectedOrderDetails.user.fullName }}
Email: {{ selectedOrderDetails.user.email }}
{{ getDoctorName(selectedOrderDetails.doctorId) || selectedOrderDetails.doctorId || 'N/A' }}
{{ formatDate(selectedOrderDetails.createdAt) }}
{{ formatDate(selectedOrderDetails.updatedAt) }}
{{ item.name || item.productName || 'Unknown Product' }}
Quantity: {{ item.quantity }}
Price: €{{ (item.price / 100).toFixed(2) }}
€{{ ((item.price * item.quantity) / 100).toFixed(2) }}
No items in this order
{{ selectedOrderDetails.pharmacy.name || 'N/A' }}
{{ selectedOrderDetails.pharmacy.street }}
{{ selectedOrderDetails.pharmacy.zip }} {{ selectedOrderDetails.pharmacy.city }}
{{ selectedOrderDetails.pharmacy.email }}
Prescription ID: {{ selectedOrderDetails.cannaflow.prescriptionId }}
Checkout URL: View Checkout
Transmission Status: {{ selectedOrderDetails.cannaflowTransmissionStatus }}
View Raw JSON Data
{{ JSON.stringify(selectedOrderDetails, null, 2) }}

KPI Reference Guide

All Key Performance Indicators tracked in reporting (~130 metrics)

ORDERS (6 KPIs)

KPI Name KPI ID Description
Total Ordersorders_totalAll orders created in the period
Total Paid Ordersorders_paid_totalOrders with confirmed payment
Total Approved Ordersorders_approved_totalOrders approved by doctor
Total Declined Ordersorders_declined_totalOrders declined by doctor
Total Cancelled Ordersorders_cancelled_totalOrders cancelled by user/admin
Total Pending Ordersorders_pending_totalOrders awaiting action

REVENUE (2 KPIs)

KPI Name KPI ID Description
Total Paid Revenue (EUR)revenue_paid_totalRevenue from paid orders
Total Approved Revenue (EUR)revenue_approved_totalRevenue from approved orders

FLOWERS (4 KPIs)

KPI Name KPI ID Description
Flower Orders Totalflowers_orders_totalNumber of flower line items
Flower Revenue Total (EUR)flowers_revenue_totalTotal revenue from flowers
Flower Grams Totalflowers_grams_totalTotal grams sold
Avg Price per Gram (EUR/g)flowers_avg_price_per_gramAverage price per gram

EXTRACTS (4 KPIs)

KPI Name KPI ID Description
Extract Orders Totalextracts_orders_totalNumber of extract line items
Extract Revenue Total (EUR)extracts_revenue_totalTotal revenue from extracts
Extract ml Totalextracts_ml_totalTotal milliliters sold
Avg Price per ml (EUR/ml)extracts_avg_price_per_mlAverage price per ml

EDIBLES (4 KPIs)

KPI Name KPI ID Description
Edibles Orders Totaledibles_orders_totalNumber of edible line items
Edibles Revenue Total (EUR)edibles_revenue_totalTotal revenue from edibles
Edibles Pieces Totaledibles_pieces_totalTotal pieces sold
Avg Price per piece (EUR/piece)edibles_avg_price_per_pieceAverage price per piece

TOP BRANDS (21 KPIs)

For each of the top 5 brands: Name, Grams, % of Total, Revenue

KPI Pattern KPI ID Pattern Description
TOP #N Brand - Nametop_brand_N_nameBrand name (N = 1-5)
TOP #N Brand - Gramstop_brand_N_gramsGrams sold by brand
TOP #N Brand - % of Totaltop_brand_N_grams_pctPercentage of total grams
TOP #N Brand - Revenue (EUR)top_brand_N_revenueRevenue from brand
TOP 5 Brands - % of Totaltop_5_brands_grams_pctCombined % of top 5 brands

TOP STRAINS (21 KPIs)

For each of the top 5 strains: Name, Grams, % of Total, Revenue

KPI Pattern KPI ID Pattern Description
TOP #N Strain - Nametop_strain_N_nameStrain/cultivar name (N = 1-5)
TOP #N Strain - Gramstop_strain_N_gramsGrams sold of strain
TOP #N Strain - % of Totaltop_strain_N_grams_pctPercentage of total grams
TOP #N Strain - Revenue (EUR)top_strain_N_revenueRevenue from strain
TOP 5 Strains - % of Totaltop_5_strains_grams_pctCombined % of top 5 strains

TOP PHARMACIES (50 KPIs)

For each of the top 10 pharmacies: Name, Orders, Grams, Revenue, % of Revenue

KPI Pattern KPI ID Pattern Description
TOP #N Pharmacy - Nametop_pharmacy_N_namePharmacy name (N = 1-10)
TOP #N Pharmacy - Orderstop_pharmacy_N_ordersNumber of orders
TOP #N Pharmacy - Gramstop_pharmacy_N_gramsTotal grams dispensed
TOP #N Pharmacy - Revenue (EUR)top_pharmacy_N_revenueRevenue from pharmacy
TOP #N Pharmacy - % of Revenuetop_pharmacy_N_revenue_pctPercentage of total revenue

USER DEMOGRAPHICS (11 KPIs)

KPI Name KPI ID Description
Users Age 18-25users_age_18_25Users in age group 18-25
Users Age 26-35users_age_26_35Users in age group 26-35
Users Age 36-45users_age_36_45Users in age group 36-45
Users Age 46-55users_age_46_55Users in age group 46-55
Users Age 56+users_age_56_plusUsers age 56 and older
Top 6 Cities (users_location_1 to users_location_6) - User count per city

USER BEHAVIOR (7 KPIs)

KPI Name KPI ID Description
New Users This Weekusers_new_this_weekUsers who registered in the period
Active Users This Weekusers_active_this_weekUnique users who placed orders
Avg Orders per Userusers_avg_orders_per_userTotal orders / active users
Repeat Usersusers_repeat_countUsers who ordered multiple days in period
Repeat Users (%)users_repeat_pctPercentage of repeat users
Avg Revenue per User (EUR)users_avg_revenue_per_userTotal revenue / active users
Avg Grams per Userusers_avg_grams_per_userTotal grams / active users

Total KPIs Summary

Orders: 6
Revenue: 2
Flowers: 4
Extracts: 4
Edibles: 4
Top Brands: 21
Top Strains: 21
Top Pharmacies: 50
Demographics: 11
User Behavior: 7
Total: ~130 KPIs