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 }}

{{ selectedTemplate.cannaflowIds?.length || 0 }} variants

No variants found for this template

{{ variant.name }} {{ 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) }}
ID: {{ variant.cannaflowId }}
{{ variant.country }} Irradiated
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

Google Sheet Import

Import templates & variants from the product spreadsheet

{{ sheetImportResult.message }} {{ sheetImportResult.success ? 'Success' : 'Failed' }}

{{ sheetImportResult.stats?.totalRows || 0 }}

Rows

{{ sheetImportResult.stats?.templatesCreated || 0 }}

Templates Created

{{ sheetImportResult.stats?.templatesUpdated || 0 }}

Templates Updated

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

Variants Created

{{ sheetImportResult.stats?.skipped || 0 }}

Skipped

⚠️ {{ sheetImportResult.stats.errors.length }} errors:

Row {{ err.row }}: {{ err.error }}
... and {{ sheetImportResult.stats.errors.length - 5 }} more
Duration: {{ sheetImportResult.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' }}

Last Sheet Import

{{ variantSyncStatus.lastSheetImport ? formatDateTime(variantSyncStatus.lastSheetImport) : '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

Sheet Import

  • • Imports from Google Spreadsheet
  • • Creates templates & variants
  • • Use "Dry Run" to preview
  • • Run after updating the sheet
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.

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.

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' }}

Loading report data...

{{ reportingError }}

Total Orders

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

{{ getTrendIcon(reportingData.stats?.orders?.total, reportingData.previousWeekStats?.orders?.total) }} {{ getTrendValue(reportingData.stats?.orders?.total, reportingData.previousWeekStats?.orders?.total) }} vs last week

Paid Orders

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

Revenue

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

Total Grams

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

Active Users

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

KPIs

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

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 This Week {{ reportingData.stats?.users?.newThisWeek || 0 }}
Avg Orders per User {{ reportingData.stats?.users?.avgOrdersPerUser?.toFixed(2) || 0 }}
Repeat Users {{ reportingData.stats?.users?.repeatUsersPercentage?.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 }} orders
{{ 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 }} orders
{{ 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 }} orders
{{ 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

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) }}