django-cfg 1.3.9__py3-none-any.whl → 1.3.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/payments/admin/networks_admin.py +12 -1
- django_cfg/apps/payments/admin/payments_admin.py +13 -0
- django_cfg/apps/payments/admin_interface/serializers/payment_serializers.py +62 -14
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_card.html +121 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/payment_qr_code.html +95 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/progress_bar.html +37 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/provider_stats.html +60 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_badge.html +41 -0
- django_cfg/apps/payments/admin_interface/templates/payments/components/status_overview.html +83 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_detail.html +363 -0
- django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +33 -3
- django_cfg/apps/payments/admin_interface/views/api/payments.py +102 -0
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +96 -45
- django_cfg/apps/payments/admin_interface/views/forms.py +5 -1
- django_cfg/apps/payments/config/__init__.py +14 -15
- django_cfg/apps/payments/config/django_cfg_integration.py +59 -1
- django_cfg/apps/payments/config/helpers.py +8 -13
- django_cfg/apps/payments/migrations/0001_initial.py +33 -46
- django_cfg/apps/payments/migrations/0002_rename_payments_un_user_id_7f6e79_idx_payments_un_user_id_8ce187_idx_and_more.py +46 -0
- django_cfg/apps/payments/migrations/0003_universalpayment_status_changed_at.py +25 -0
- django_cfg/apps/payments/models/managers/payment_managers.py +142 -25
- django_cfg/apps/payments/models/payments.py +94 -0
- django_cfg/apps/payments/services/core/base.py +4 -4
- django_cfg/apps/payments/services/core/payment_service.py +265 -38
- django_cfg/apps/payments/services/providers/base.py +209 -3
- django_cfg/apps/payments/services/providers/models/__init__.py +2 -0
- django_cfg/apps/payments/services/providers/models/base.py +25 -2
- django_cfg/apps/payments/services/providers/nowpayments/models.py +2 -2
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +57 -9
- django_cfg/apps/payments/services/providers/registry.py +5 -5
- django_cfg/apps/payments/services/types/requests.py +19 -7
- django_cfg/apps/payments/signals/payment_signals.py +31 -2
- django_cfg/apps/payments/static/payments/js/api-client.js +6 -1
- django_cfg/apps/payments/static/payments/js/payment-detail.js +167 -0
- django_cfg/apps/payments/static/payments/js/payment-form.js +35 -26
- django_cfg/apps/payments/templatetags/payment_tags.py +8 -0
- django_cfg/apps/payments/urls.py +3 -2
- django_cfg/apps/payments/views/api/currencies.py +3 -0
- django_cfg/apps/payments/views/serializers/currencies.py +18 -5
- django_cfg/apps/tasks/admin/tasks_admin.py +2 -2
- django_cfg/apps/tasks/static/tasks/css/dashboard.css +68 -217
- django_cfg/apps/tasks/static/tasks/js/api.js +40 -84
- django_cfg/apps/tasks/static/tasks/js/components/DataManager.js +24 -0
- django_cfg/apps/tasks/static/tasks/js/components/TabManager.js +85 -0
- django_cfg/apps/tasks/static/tasks/js/components/TaskRenderer.js +216 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/main.mjs +245 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/overview.mjs +123 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/queues.mjs +120 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/tasks.mjs +350 -0
- django_cfg/apps/tasks/static/tasks/js/dashboard/workers.mjs +169 -0
- django_cfg/apps/tasks/tasks/__init__.py +10 -0
- django_cfg/apps/tasks/tasks/demo_tasks.py +133 -0
- django_cfg/apps/tasks/templates/tasks/components/management_actions.html +42 -45
- django_cfg/apps/tasks/templates/tasks/components/{status_cards.html → overview_content.html} +30 -11
- django_cfg/apps/tasks/templates/tasks/components/queues_content.html +19 -0
- django_cfg/apps/tasks/templates/tasks/components/tab_navigation.html +16 -10
- django_cfg/apps/tasks/templates/tasks/components/tasks_content.html +51 -0
- django_cfg/apps/tasks/templates/tasks/components/workers_content.html +30 -0
- django_cfg/apps/tasks/templates/tasks/layout/base.html +117 -0
- django_cfg/apps/tasks/templates/tasks/pages/dashboard.html +82 -0
- django_cfg/apps/tasks/templates/tasks/partials/task_row_template.html +40 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_filters.html +37 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_footer.html +41 -0
- django_cfg/apps/tasks/templates/tasks/widgets/task_table.html +50 -0
- django_cfg/apps/tasks/urls.py +2 -2
- django_cfg/apps/tasks/urls_admin.py +2 -2
- django_cfg/apps/tasks/utils/__init__.py +1 -0
- django_cfg/apps/tasks/utils/simulator.py +356 -0
- django_cfg/apps/tasks/views/__init__.py +16 -0
- django_cfg/apps/tasks/views/api.py +569 -0
- django_cfg/apps/tasks/views/dashboard.py +58 -0
- django_cfg/core/integration/__init__.py +21 -0
- django_cfg/management/commands/rundramatiq_simulator.py +430 -0
- django_cfg/models/constance.py +0 -11
- django_cfg/models/payments.py +137 -3
- django_cfg/modules/django_tasks.py +54 -21
- django_cfg/registry/core.py +4 -9
- django_cfg/template_archive/django_sample.zip +0 -0
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/METADATA +2 -2
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/RECORD +84 -152
- django_cfg/apps/payments/config/constance/__init__.py +0 -22
- django_cfg/apps/payments/config/constance/config_service.py +0 -123
- django_cfg/apps/payments/config/constance/fields.py +0 -69
- django_cfg/apps/payments/config/constance/settings.py +0 -160
- django_cfg/apps/payments/migrations/0002_currency_usd_rate_currency_usd_rate_updated_at.py +0 -26
- django_cfg/apps/payments/migrations/0003_remove_provider_currency_fields.py +0 -28
- django_cfg/apps/payments/migrations/0004_add_reserved_usd_field.py +0 -30
- django_cfg/apps/tasks/static/tasks/js/dashboard.js +0 -614
- django_cfg/apps/tasks/static/tasks/js/modals.js +0 -452
- django_cfg/apps/tasks/static/tasks/js/notifications.js +0 -144
- django_cfg/apps/tasks/static/tasks/js/task-monitor.js +0 -454
- django_cfg/apps/tasks/static/tasks/js/theme.js +0 -77
- django_cfg/apps/tasks/templates/tasks/base.html +0 -96
- django_cfg/apps/tasks/templates/tasks/components/info_cards.html +0 -85
- django_cfg/apps/tasks/templates/tasks/components/overview_tab.html +0 -22
- django_cfg/apps/tasks/templates/tasks/components/queues_tab.html +0 -19
- django_cfg/apps/tasks/templates/tasks/components/task_details_modal.html +0 -103
- django_cfg/apps/tasks/templates/tasks/components/tasks_tab.html +0 -32
- django_cfg/apps/tasks/templates/tasks/components/workers_tab.html +0 -29
- django_cfg/apps/tasks/templates/tasks/dashboard.html +0 -29
- django_cfg/apps/tasks/views.py +0 -461
- django_cfg/management/commands/app_agent_diagnose.py +0 -470
- django_cfg/management/commands/app_agent_generate.py +0 -342
- django_cfg/management/commands/app_agent_info.py +0 -308
- django_cfg/management/commands/auto_generate.py +0 -486
- django_cfg/modules/django_app_agent/__init__.py +0 -87
- django_cfg/modules/django_app_agent/agents/__init__.py +0 -40
- django_cfg/modules/django_app_agent/agents/base/__init__.py +0 -24
- django_cfg/modules/django_app_agent/agents/base/agent.py +0 -354
- django_cfg/modules/django_app_agent/agents/base/context.py +0 -236
- django_cfg/modules/django_app_agent/agents/base/executor.py +0 -430
- django_cfg/modules/django_app_agent/agents/generation/__init__.py +0 -12
- django_cfg/modules/django_app_agent/agents/generation/app_generator/__init__.py +0 -15
- django_cfg/modules/django_app_agent/agents/generation/app_generator/config_validator.py +0 -147
- django_cfg/modules/django_app_agent/agents/generation/app_generator/main.py +0 -99
- django_cfg/modules/django_app_agent/agents/generation/app_generator/models.py +0 -32
- django_cfg/modules/django_app_agent/agents/generation/app_generator/prompt_manager.py +0 -290
- django_cfg/modules/django_app_agent/agents/interfaces.py +0 -376
- django_cfg/modules/django_app_agent/core/__init__.py +0 -33
- django_cfg/modules/django_app_agent/core/config.py +0 -300
- django_cfg/modules/django_app_agent/core/exceptions.py +0 -359
- django_cfg/modules/django_app_agent/models/__init__.py +0 -71
- django_cfg/modules/django_app_agent/models/base.py +0 -283
- django_cfg/modules/django_app_agent/models/context.py +0 -496
- django_cfg/modules/django_app_agent/models/enums.py +0 -481
- django_cfg/modules/django_app_agent/models/requests.py +0 -500
- django_cfg/modules/django_app_agent/models/responses.py +0 -585
- django_cfg/modules/django_app_agent/pytest.ini +0 -6
- django_cfg/modules/django_app_agent/services/__init__.py +0 -42
- django_cfg/modules/django_app_agent/services/app_generator/__init__.py +0 -30
- django_cfg/modules/django_app_agent/services/app_generator/ai_integration.py +0 -133
- django_cfg/modules/django_app_agent/services/app_generator/context.py +0 -40
- django_cfg/modules/django_app_agent/services/app_generator/main.py +0 -202
- django_cfg/modules/django_app_agent/services/app_generator/structure.py +0 -316
- django_cfg/modules/django_app_agent/services/app_generator/validation.py +0 -125
- django_cfg/modules/django_app_agent/services/base.py +0 -437
- django_cfg/modules/django_app_agent/services/context_builder/__init__.py +0 -34
- django_cfg/modules/django_app_agent/services/context_builder/code_extractor.py +0 -141
- django_cfg/modules/django_app_agent/services/context_builder/context_generator.py +0 -276
- django_cfg/modules/django_app_agent/services/context_builder/main.py +0 -272
- django_cfg/modules/django_app_agent/services/context_builder/models.py +0 -40
- django_cfg/modules/django_app_agent/services/context_builder/pattern_analyzer.py +0 -85
- django_cfg/modules/django_app_agent/services/project_scanner/__init__.py +0 -31
- django_cfg/modules/django_app_agent/services/project_scanner/app_discovery.py +0 -311
- django_cfg/modules/django_app_agent/services/project_scanner/main.py +0 -221
- django_cfg/modules/django_app_agent/services/project_scanner/models.py +0 -59
- django_cfg/modules/django_app_agent/services/project_scanner/pattern_detection.py +0 -94
- django_cfg/modules/django_app_agent/services/questioning_service/__init__.py +0 -28
- django_cfg/modules/django_app_agent/services/questioning_service/main.py +0 -273
- django_cfg/modules/django_app_agent/services/questioning_service/models.py +0 -111
- django_cfg/modules/django_app_agent/services/questioning_service/question_generator.py +0 -251
- django_cfg/modules/django_app_agent/services/questioning_service/response_processor.py +0 -347
- django_cfg/modules/django_app_agent/services/questioning_service/session_manager.py +0 -356
- django_cfg/modules/django_app_agent/services/report_service.py +0 -332
- django_cfg/modules/django_app_agent/services/template_manager/__init__.py +0 -18
- django_cfg/modules/django_app_agent/services/template_manager/jinja_engine.py +0 -236
- django_cfg/modules/django_app_agent/services/template_manager/main.py +0 -159
- django_cfg/modules/django_app_agent/services/template_manager/models.py +0 -36
- django_cfg/modules/django_app_agent/services/template_manager/template_loader.py +0 -100
- django_cfg/modules/django_app_agent/services/template_manager/templates/admin.py.j2 +0 -105
- django_cfg/modules/django_app_agent/services/template_manager/templates/apps.py.j2 +0 -31
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_config.py.j2 +0 -44
- django_cfg/modules/django_app_agent/services/template_manager/templates/cfg_module.py.j2 +0 -81
- django_cfg/modules/django_app_agent/services/template_manager/templates/forms.py.j2 +0 -107
- django_cfg/modules/django_app_agent/services/template_manager/templates/models.py.j2 +0 -139
- django_cfg/modules/django_app_agent/services/template_manager/templates/serializers.py.j2 +0 -91
- django_cfg/modules/django_app_agent/services/template_manager/templates/tests.py.j2 +0 -195
- django_cfg/modules/django_app_agent/services/template_manager/templates/urls.py.j2 +0 -35
- django_cfg/modules/django_app_agent/services/template_manager/templates/views.py.j2 +0 -211
- django_cfg/modules/django_app_agent/services/template_manager/variable_processor.py +0 -200
- django_cfg/modules/django_app_agent/services/validation_service/__init__.py +0 -25
- django_cfg/modules/django_app_agent/services/validation_service/django_validator.py +0 -333
- django_cfg/modules/django_app_agent/services/validation_service/main.py +0 -242
- django_cfg/modules/django_app_agent/services/validation_service/models.py +0 -66
- django_cfg/modules/django_app_agent/services/validation_service/quality_validator.py +0 -352
- django_cfg/modules/django_app_agent/services/validation_service/security_validator.py +0 -272
- django_cfg/modules/django_app_agent/services/validation_service/syntax_validator.py +0 -203
- django_cfg/modules/django_app_agent/ui/__init__.py +0 -25
- django_cfg/modules/django_app_agent/ui/cli.py +0 -419
- django_cfg/modules/django_app_agent/ui/rich_components.py +0 -622
- django_cfg/modules/django_app_agent/utils/__init__.py +0 -38
- django_cfg/modules/django_app_agent/utils/logging.py +0 -360
- django_cfg/modules/django_app_agent/utils/validation.py +0 -417
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.9.dist-info → django_cfg-1.3.11.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
@@ -143,6 +143,7 @@ class ProviderCurrencyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
143
143
|
# List configuration
|
144
144
|
list_display = [
|
145
145
|
'currency_display',
|
146
|
+
'provider_currency_code_display',
|
146
147
|
'network_display',
|
147
148
|
'provider_display',
|
148
149
|
'fees_display',
|
@@ -163,7 +164,8 @@ class ProviderCurrencyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
163
164
|
'currency__name',
|
164
165
|
'currency__symbol',
|
165
166
|
'network__name',
|
166
|
-
'provider'
|
167
|
+
'provider',
|
168
|
+
'provider_currency_code'
|
167
169
|
]
|
168
170
|
|
169
171
|
readonly_fields = [
|
@@ -181,6 +183,15 @@ class ProviderCurrencyAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
181
183
|
icon=Icons.CURRENCY_BITCOIN
|
182
184
|
)
|
183
185
|
|
186
|
+
@display(description="Provider Code")
|
187
|
+
def provider_currency_code_display(self, obj: ProviderCurrency):
|
188
|
+
"""Provider currency code display."""
|
189
|
+
return StatusBadge.create(
|
190
|
+
text=obj.provider_currency_code,
|
191
|
+
variant="warning",
|
192
|
+
icon=Icons.CODE
|
193
|
+
)
|
194
|
+
|
184
195
|
@display(description="Network")
|
185
196
|
def network_display(self, obj: ProviderCurrency):
|
186
197
|
"""Network display."""
|
@@ -56,6 +56,7 @@ class UniversalPaymentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
56
56
|
'amount_display',
|
57
57
|
'status_display',
|
58
58
|
'provider_display',
|
59
|
+
'status_changed_display',
|
59
60
|
'created_display'
|
60
61
|
]
|
61
62
|
|
@@ -78,6 +79,7 @@ class UniversalPaymentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
78
79
|
'internal_payment_id',
|
79
80
|
'created_at',
|
80
81
|
'updated_at',
|
82
|
+
'status_changed_at',
|
81
83
|
'payment_details_display'
|
82
84
|
]
|
83
85
|
|
@@ -168,6 +170,17 @@ class UniversalPaymentAdmin(OptimizedModelAdmin, DisplayMixin, ModelAdmin):
|
|
168
170
|
DateTimeDisplayConfig(show_relative=True, show_seconds=False)
|
169
171
|
)
|
170
172
|
|
173
|
+
@display(description="Status Changed")
|
174
|
+
def status_changed_display(self, obj):
|
175
|
+
"""Status changed time display."""
|
176
|
+
if not obj.status_changed_at:
|
177
|
+
return "-"
|
178
|
+
return self.display_datetime_relative(
|
179
|
+
obj,
|
180
|
+
'status_changed_at',
|
181
|
+
DateTimeDisplayConfig(show_relative=True, show_seconds=False)
|
182
|
+
)
|
183
|
+
|
171
184
|
# Readonly field displays
|
172
185
|
def payment_details_display(self, obj):
|
173
186
|
"""Detailed payment information for detail view."""
|
@@ -6,6 +6,8 @@ DRF serializers for payment management in admin dashboard.
|
|
6
6
|
|
7
7
|
from rest_framework import serializers
|
8
8
|
from django.contrib.auth import get_user_model
|
9
|
+
from django.contrib.humanize.templatetags.humanize import naturaltime
|
10
|
+
|
9
11
|
from ...models import UniversalPayment
|
10
12
|
|
11
13
|
User = get_user_model()
|
@@ -90,6 +92,7 @@ class AdminPaymentDetailSerializer(serializers.Serializer):
|
|
90
92
|
security_nonce = serializers.CharField(read_only=True)
|
91
93
|
expires_at = serializers.DateTimeField(read_only=True)
|
92
94
|
completed_at = serializers.DateTimeField(read_only=True)
|
95
|
+
status_changed_at = serializers.DateTimeField(read_only=True)
|
93
96
|
description = serializers.CharField(read_only=True)
|
94
97
|
callback_url = serializers.URLField(read_only=True)
|
95
98
|
cancel_url = serializers.URLField(read_only=True)
|
@@ -117,7 +120,6 @@ class AdminPaymentDetailSerializer(serializers.Serializer):
|
|
117
120
|
|
118
121
|
def get_age(self, obj):
|
119
122
|
"""Get human-readable age of payment."""
|
120
|
-
from django.contrib.humanize.templatetags.humanize import naturaltime
|
121
123
|
return naturaltime(obj.created_at)
|
122
124
|
|
123
125
|
|
@@ -128,7 +130,7 @@ class AdminPaymentCreateSerializer(serializers.Serializer):
|
|
128
130
|
"""
|
129
131
|
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
|
130
132
|
amount_usd = serializers.FloatField(min_value=1.0, max_value=100000.0)
|
131
|
-
currency_code = serializers.CharField(max_length=
|
133
|
+
currency_code = serializers.CharField(max_length=20, help_text="Provider currency code (e.g., BTC, ZROERC20)", write_only=True)
|
132
134
|
provider = serializers.CharField(max_length=50)
|
133
135
|
description = serializers.CharField(required=False, allow_blank=True)
|
134
136
|
callback_url = serializers.URLField(required=False, allow_blank=True)
|
@@ -143,22 +145,67 @@ class AdminPaymentCreateSerializer(serializers.Serializer):
|
|
143
145
|
return value
|
144
146
|
|
145
147
|
def create(self, validated_data):
|
146
|
-
"""Create payment
|
147
|
-
from django_cfg.apps.payments.
|
148
|
-
import
|
148
|
+
"""Create payment using PaymentService for proper provider integration."""
|
149
|
+
from django_cfg.apps.payments.services.core.payment_service import PaymentService
|
150
|
+
from django_cfg.apps.payments.services.types.requests import PaymentCreateRequest
|
151
|
+
from django_cfg.apps.payments.models import ProviderCurrency
|
152
|
+
|
153
|
+
# Extract provider_currency_code and find original currency
|
154
|
+
provider_currency_code = validated_data.pop('currency_code')
|
149
155
|
|
150
|
-
#
|
151
|
-
currency_code = validated_data.pop('currency_code')
|
156
|
+
# Find the ProviderCurrency to get original currency code
|
152
157
|
try:
|
153
|
-
|
154
|
-
|
155
|
-
|
158
|
+
provider_currency = ProviderCurrency.objects.select_related('currency').get(
|
159
|
+
provider_currency_code=provider_currency_code,
|
160
|
+
provider=validated_data['provider'],
|
161
|
+
is_enabled=True
|
162
|
+
)
|
163
|
+
original_currency_code = provider_currency.currency.code
|
164
|
+
except ProviderCurrency.DoesNotExist:
|
165
|
+
raise serializers.ValidationError(f"Provider currency {provider_currency_code} not found for {validated_data['provider']}")
|
156
166
|
|
157
|
-
#
|
158
|
-
|
159
|
-
|
167
|
+
# Create PaymentCreateRequest for the service
|
168
|
+
payment_request = PaymentCreateRequest(
|
169
|
+
user_id=validated_data['user'].id,
|
170
|
+
amount_usd=validated_data['amount_usd'],
|
171
|
+
currency_code=original_currency_code, # Use original currency code
|
172
|
+
provider=validated_data['provider'],
|
173
|
+
description=validated_data.get('description', ''),
|
174
|
+
callback_url=validated_data.get('callback_url', ''),
|
175
|
+
cancel_url=validated_data.get('cancel_url', ''),
|
176
|
+
metadata={'provider_currency_code': provider_currency_code} # Store provider code in metadata
|
177
|
+
)
|
160
178
|
|
161
|
-
|
179
|
+
# Use PaymentService to create payment with provider integration
|
180
|
+
payment_service = PaymentService()
|
181
|
+
result = payment_service.create_payment(payment_request)
|
182
|
+
|
183
|
+
if result.success:
|
184
|
+
# Get the created payment object from database using payment_id
|
185
|
+
from django_cfg.apps.payments.models import UniversalPayment
|
186
|
+
payment = UniversalPayment.objects.get(id=result.payment_id)
|
187
|
+
return payment
|
188
|
+
else:
|
189
|
+
raise serializers.ValidationError(f"Payment creation failed: {result.message}")
|
190
|
+
|
191
|
+
def to_representation(self, instance):
|
192
|
+
"""Return created payment with ID and currency info."""
|
193
|
+
if instance:
|
194
|
+
return {
|
195
|
+
'id': str(instance.id),
|
196
|
+
'user': instance.user.id,
|
197
|
+
'amount_usd': instance.amount_usd,
|
198
|
+
'currency_code': instance.currency.code if instance.currency else None,
|
199
|
+
'currency_name': instance.currency.name if instance.currency else None,
|
200
|
+
'provider': instance.provider,
|
201
|
+
'description': instance.description,
|
202
|
+
'callback_url': instance.callback_url,
|
203
|
+
'cancel_url': instance.cancel_url,
|
204
|
+
'internal_payment_id': instance.internal_payment_id,
|
205
|
+
'status': instance.status,
|
206
|
+
'created_at': instance.created_at.isoformat() if instance.created_at else None
|
207
|
+
}
|
208
|
+
return super().to_representation(instance)
|
162
209
|
|
163
210
|
|
164
211
|
class AdminPaymentUpdateSerializer(serializers.ModelSerializer):
|
@@ -178,6 +225,7 @@ class AdminPaymentUpdateSerializer(serializers.ModelSerializer):
|
|
178
225
|
if value != UniversalPayment.PaymentStatus.COMPLETED:
|
179
226
|
raise serializers.ValidationError("Cannot change status of completed payment")
|
180
227
|
return value
|
228
|
+
|
181
229
|
|
182
230
|
|
183
231
|
class AdminPaymentStatsSerializer(serializers.Serializer):
|
@@ -0,0 +1,121 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Payment Card Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/payment_card.html' with payment=payment show_actions=True %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- payment: UniversalPayment object
|
9
|
+
- show_actions: Boolean to show/hide action buttons (default: False)
|
10
|
+
{% endcomment %}
|
11
|
+
|
12
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-6"
|
13
|
+
data-payment-id="{{ payment.id }}">
|
14
|
+
|
15
|
+
<!-- Header -->
|
16
|
+
<div class="flex items-center justify-between mb-4">
|
17
|
+
<div class="payment-id">
|
18
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Payment</span>
|
19
|
+
<p class="text-lg font-semibold text-gray-900 dark:text-white">
|
20
|
+
#{{ payment.internal_payment_id|default:payment.id|truncatechars:8 }}
|
21
|
+
</p>
|
22
|
+
</div>
|
23
|
+
{% include 'payments/components/status_badge.html' with payment=payment %}
|
24
|
+
</div>
|
25
|
+
|
26
|
+
<!-- Amount -->
|
27
|
+
<div class="payment-amount mb-4">
|
28
|
+
<div class="flex items-baseline">
|
29
|
+
<span class="text-3xl font-bold text-gray-900 dark:text-white">
|
30
|
+
${{ payment.amount_usd|floatformat:2 }}
|
31
|
+
</span>
|
32
|
+
<span class="ml-2 text-lg text-gray-500 dark:text-gray-400">USD</span>
|
33
|
+
</div>
|
34
|
+
{% if payment.currency.code != 'USD' and payment.pay_amount %}
|
35
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
36
|
+
{{ payment.pay_amount|floatformat:8 }} {{ payment.currency.code }}
|
37
|
+
</div>
|
38
|
+
{% endif %}
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<!-- Payment Details -->
|
42
|
+
<div class="payment-details space-y-2 mb-4">
|
43
|
+
<div class="flex justify-between">
|
44
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Provider</span>
|
45
|
+
<div class="flex items-center">
|
46
|
+
<div class="w-2 h-2 rounded-full mr-2
|
47
|
+
{% if payment.provider == 'cryptapi' %}bg-orange-500
|
48
|
+
{% elif payment.provider == 'cryptomus' %}bg-blue-500
|
49
|
+
{% elif payment.provider == 'stripe' %}bg-purple-500
|
50
|
+
{% elif payment.provider == 'nowpayments' %}bg-green-500
|
51
|
+
{% else %}bg-gray-500{% endif %}">
|
52
|
+
</div>
|
53
|
+
<span class="text-sm font-medium text-gray-900 dark:text-white capitalize">
|
54
|
+
{{ payment.provider }}
|
55
|
+
</span>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
<div class="flex justify-between">
|
59
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Created</span>
|
60
|
+
<span class="text-sm text-gray-900 dark:text-white">
|
61
|
+
{{ payment.created_at|date:"M d, H:i" }}
|
62
|
+
</span>
|
63
|
+
</div>
|
64
|
+
{% if payment.status_changed_at %}
|
65
|
+
<div class="flex justify-between">
|
66
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Status Changed</span>
|
67
|
+
<span class="text-sm text-gray-900 dark:text-white">
|
68
|
+
{{ payment.status_changed_at|date:"M d, H:i" }}
|
69
|
+
</span>
|
70
|
+
</div>
|
71
|
+
{% endif %}
|
72
|
+
{% if payment.processed_at %}
|
73
|
+
<div class="flex justify-between">
|
74
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Processed</span>
|
75
|
+
<span class="text-sm text-gray-900 dark:text-white">
|
76
|
+
{{ payment.processed_at|date:"M d, H:i" }}
|
77
|
+
</span>
|
78
|
+
</div>
|
79
|
+
{% endif %}
|
80
|
+
{% if payment.pay_address %}
|
81
|
+
<div class="flex justify-between">
|
82
|
+
<span class="text-sm text-gray-500 dark:text-gray-400">Address</span>
|
83
|
+
<span class="text-sm font-mono text-gray-900 dark:text-white">
|
84
|
+
{{ payment.pay_address|truncatechars:16 }}
|
85
|
+
<button onclick="navigator.clipboard.writeText('{{ payment.pay_address }}')"
|
86
|
+
class="ml-1 text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300">
|
87
|
+
<span class="material-icons-outlined text-xs">content_copy</span>
|
88
|
+
</button>
|
89
|
+
</span>
|
90
|
+
</div>
|
91
|
+
{% endif %}
|
92
|
+
</div>
|
93
|
+
|
94
|
+
<!-- Progress Bar -->
|
95
|
+
{% include 'payments/components/progress_bar.html' with payment=payment %}
|
96
|
+
|
97
|
+
<!-- Actions -->
|
98
|
+
{% if show_actions %}
|
99
|
+
<div class="payment-actions mt-4 flex space-x-2">
|
100
|
+
<a href="{% url 'cfg_payments_admin:payment-detail' payment.id %}"
|
101
|
+
class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200 text-center">
|
102
|
+
<span class="material-icons-outlined text-sm mr-1">visibility</span>
|
103
|
+
View Details
|
104
|
+
</a>
|
105
|
+
{% if payment.status == 'pending' or payment.status == 'confirming' %}
|
106
|
+
<button class="px-3 py-1.5 text-xs bg-red-600 text-white hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600 rounded-md transition-colors duration-200"
|
107
|
+
onclick="cancelPayment('{{ payment.id }}')">
|
108
|
+
<span class="material-icons-outlined text-sm mr-1">cancel</span>
|
109
|
+
Cancel
|
110
|
+
</button>
|
111
|
+
{% endif %}
|
112
|
+
{% if payment.pay_address %}
|
113
|
+
<button class="px-3 py-1.5 text-xs bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 rounded-md transition-colors duration-200"
|
114
|
+
onclick="showQRCode('{{ payment.id }}')">
|
115
|
+
<span class="material-icons-outlined text-sm mr-1">qr_code</span>
|
116
|
+
QR Code
|
117
|
+
</button>
|
118
|
+
{% endif %}
|
119
|
+
</div>
|
120
|
+
{% endif %}
|
121
|
+
</div>
|
@@ -0,0 +1,95 @@
|
|
1
|
+
{% load payment_tags %}
|
2
|
+
|
3
|
+
<!-- Payment QR Code Component -->
|
4
|
+
<div class="flex flex-col items-center space-y-4 p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
5
|
+
{% if payment.pay_address %}
|
6
|
+
<!-- QR Code Image -->
|
7
|
+
<div class="w-48 h-48 bg-gray-100 dark:bg-gray-700 rounded flex items-center justify-center">
|
8
|
+
{% if payment.get_qr_code_url %}
|
9
|
+
<img src="{{ payment.get_qr_code_url }}"
|
10
|
+
alt="QR Code for {{ payment.pay_address }}"
|
11
|
+
class="w-48 h-48 rounded"
|
12
|
+
onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
|
13
|
+
<div class="hidden w-48 h-48 items-center justify-center">
|
14
|
+
<span class="material-icons text-gray-400 text-4xl">qr_code_off</span>
|
15
|
+
</div>
|
16
|
+
{% else %}
|
17
|
+
<span class="material-icons text-gray-400 text-4xl">qr_code_off</span>
|
18
|
+
{% endif %}
|
19
|
+
</div>
|
20
|
+
|
21
|
+
<!-- Payment Details -->
|
22
|
+
<div class="text-center space-y-2">
|
23
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
24
|
+
Payment Address
|
25
|
+
</p>
|
26
|
+
<p class="text-xs font-mono text-gray-600 dark:text-gray-400 break-all max-w-xs">
|
27
|
+
{{ payment.pay_address }}
|
28
|
+
</p>
|
29
|
+
{% if payment.pay_amount %}
|
30
|
+
<p class="text-xs text-gray-500 dark:text-gray-500">
|
31
|
+
Amount: {{ payment.formatted_pay_amount }} {{ payment.currency.code }}
|
32
|
+
</p>
|
33
|
+
{% endif %}
|
34
|
+
|
35
|
+
<!-- QR Data Preview -->
|
36
|
+
{% if payment.qr_data != payment.pay_address %}
|
37
|
+
<p class="text-xs text-blue-600 dark:text-blue-400">
|
38
|
+
QR contains: {{ payment.qr_data|truncatechars:50 }}
|
39
|
+
</p>
|
40
|
+
{% endif %}
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<!-- Action Buttons -->
|
44
|
+
<div class="flex space-x-2 pt-2">
|
45
|
+
<button class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200"
|
46
|
+
onclick="copyToClipboard('{{ payment.pay_address }}', 'Address copied!')">
|
47
|
+
<span class="material-icons text-sm mr-1">content_copy</span>
|
48
|
+
Copy Address
|
49
|
+
</button>
|
50
|
+
{% if payment.pay_amount %}
|
51
|
+
<button class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200"
|
52
|
+
onclick="copyToClipboard('{{ payment.pay_amount }}', 'Amount copied!')">
|
53
|
+
<span class="material-icons text-sm mr-1">content_copy</span>
|
54
|
+
Copy Amount
|
55
|
+
</button>
|
56
|
+
{% endif %}
|
57
|
+
{% if payment.qr_data %}
|
58
|
+
<button class="flex-1 px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors duration-200"
|
59
|
+
onclick="copyToClipboard('{{ payment.qr_data }}', 'QR data copied!')">
|
60
|
+
<span class="material-icons text-sm mr-1">qr_code</span>
|
61
|
+
Copy QR Data
|
62
|
+
</button>
|
63
|
+
{% endif %}
|
64
|
+
</div>
|
65
|
+
|
66
|
+
|
67
|
+
{% else %}
|
68
|
+
<div class="text-center py-8">
|
69
|
+
<span class="material-icons text-gray-400 text-4xl mb-2">qr_code_off</span>
|
70
|
+
<p class="text-sm text-gray-500 dark:text-gray-400">
|
71
|
+
QR code not available for this payment
|
72
|
+
</p>
|
73
|
+
</div>
|
74
|
+
{% endif %}
|
75
|
+
</div>
|
76
|
+
|
77
|
+
<!-- Copy to clipboard utility -->
|
78
|
+
<script>
|
79
|
+
function copyToClipboard(text, message) {
|
80
|
+
navigator.clipboard.writeText(text).then(function() {
|
81
|
+
// Show success message (you can customize this)
|
82
|
+
const toast = document.createElement('div');
|
83
|
+
toast.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded shadow-lg z-50';
|
84
|
+
toast.textContent = message || 'Copied to clipboard!';
|
85
|
+
document.body.appendChild(toast);
|
86
|
+
|
87
|
+
setTimeout(() => {
|
88
|
+
document.body.removeChild(toast);
|
89
|
+
}, 2000);
|
90
|
+
}).catch(function(err) {
|
91
|
+
console.error('Failed to copy: ', err);
|
92
|
+
alert('Failed to copy to clipboard');
|
93
|
+
});
|
94
|
+
}
|
95
|
+
</script>
|
@@ -0,0 +1,37 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Payment Progress Bar Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/progress_bar.html' with payment=payment %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- payment: UniversalPayment object
|
9
|
+
{% endcomment %}
|
10
|
+
|
11
|
+
<div class="payment-progress">
|
12
|
+
<!-- Progress Bar -->
|
13
|
+
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
14
|
+
<div class="h-2 rounded-full transition-all duration-500 ease-out
|
15
|
+
{% if payment.status == 'completed' %}bg-green-600
|
16
|
+
{% elif payment.status == 'failed' %}bg-red-600
|
17
|
+
{% elif payment.status == 'pending' %}bg-yellow-600
|
18
|
+
{% elif payment.status == 'confirming' %}bg-blue-600
|
19
|
+
{% elif payment.status == 'cancelled' %}bg-gray-600
|
20
|
+
{% else %}bg-blue-600{% endif %}"
|
21
|
+
style="width: {% if payment.status == 'completed' %}100{% elif payment.status == 'failed' or payment.status == 'cancelled' %}100{% elif payment.status == 'confirming' %}75{% elif payment.status == 'pending' %}25{% else %}0{% endif %}%">
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<!-- Progress Text -->
|
26
|
+
<div class="flex justify-between items-center mt-2 text-xs text-gray-500 dark:text-gray-400">
|
27
|
+
<span>
|
28
|
+
{% if payment.status == 'completed' %}100% Complete
|
29
|
+
{% elif payment.status == 'failed' %}Failed
|
30
|
+
{% elif payment.status == 'cancelled' %}Cancelled
|
31
|
+
{% elif payment.status == 'confirming' %}75% Confirming
|
32
|
+
{% elif payment.status == 'pending' %}25% Pending
|
33
|
+
{% else %}0% Initiated{% endif %}
|
34
|
+
</span>
|
35
|
+
<span>{{ payment.get_status_display }}</span>
|
36
|
+
</div>
|
37
|
+
</div>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Provider Statistics Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/provider_stats.html' with provider_stats=provider_stats %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- provider_stats: List of provider statistics objects
|
9
|
+
{% endcomment %}
|
10
|
+
|
11
|
+
<div class="space-y-4">
|
12
|
+
{% for stat in provider_stats %}
|
13
|
+
<div class="flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700 rounded-lg border border-gray-200 dark:border-gray-600">
|
14
|
+
<div class="flex items-center space-x-3">
|
15
|
+
<!-- Provider Icon -->
|
16
|
+
<div class="w-8 h-8 rounded-full flex items-center justify-center
|
17
|
+
{% if stat.provider == 'cryptapi' %}bg-orange-500
|
18
|
+
{% elif stat.provider == 'cryptomus' %}bg-blue-500
|
19
|
+
{% elif stat.provider == 'stripe' %}bg-purple-500
|
20
|
+
{% elif stat.provider == 'nowpayments' %}bg-green-500
|
21
|
+
{% else %}bg-gray-500{% endif %}">
|
22
|
+
<span class="material-icons-outlined text-white text-sm">payment</span>
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<!-- Provider Info -->
|
26
|
+
<div>
|
27
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white capitalize">
|
28
|
+
{{ stat.provider }}
|
29
|
+
</p>
|
30
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">
|
31
|
+
{{ stat.count }} payment{{ stat.count|pluralize }}
|
32
|
+
</p>
|
33
|
+
</div>
|
34
|
+
</div>
|
35
|
+
|
36
|
+
<!-- Statistics -->
|
37
|
+
<div class="text-right">
|
38
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white">
|
39
|
+
${{ stat.volume|floatformat:2|default:"0.00" }}
|
40
|
+
</p>
|
41
|
+
<div class="flex items-center space-x-2 mt-1">
|
42
|
+
<!-- Success Rate Bar -->
|
43
|
+
<div class="w-16 bg-gray-200 dark:bg-gray-600 rounded-full h-1.5">
|
44
|
+
<div class="bg-green-600 h-1.5 rounded-full transition-all duration-300"
|
45
|
+
style="width: {{ stat.success_rate|floatformat:0 }}%"></div>
|
46
|
+
</div>
|
47
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
48
|
+
{{ stat.success_rate|floatformat:0 }}%
|
49
|
+
</span>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</div>
|
53
|
+
{% empty %}
|
54
|
+
<div class="text-center py-8">
|
55
|
+
<span class="material-icons-outlined text-gray-400 text-4xl mb-2">bar_chart</span>
|
56
|
+
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">No Provider Data</p>
|
57
|
+
<p class="text-xs text-gray-500 dark:text-gray-400">Provider statistics will appear here once payments are processed</p>
|
58
|
+
</div>
|
59
|
+
{% endfor %}
|
60
|
+
</div>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Payment Status Badge Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/status_badge.html' with payment=payment %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- payment: UniversalPayment object
|
9
|
+
{% endcomment %}
|
10
|
+
|
11
|
+
<span class="payment-status-badge inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
12
|
+
{% if payment.status == 'pending' %}bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-400
|
13
|
+
{% elif payment.status == 'confirming' %}bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-400
|
14
|
+
{% elif payment.status == 'confirmed' or payment.status == 'completed' %}bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400
|
15
|
+
{% elif payment.status == 'failed' %}bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400
|
16
|
+
{% elif payment.status == 'expired' or payment.status == 'cancelled' %}bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400
|
17
|
+
{% elif payment.status == 'refunded' %}bg-purple-100 text-purple-800 dark:bg-purple-900/20 dark:text-purple-400
|
18
|
+
{% else %}bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-400{% endif %}"
|
19
|
+
data-status="{{ payment.status }}">
|
20
|
+
{% if payment.status == 'pending' %}
|
21
|
+
<span class="material-icons-outlined text-sm mr-1 animate-pulse">pending</span>
|
22
|
+
{% elif payment.status == 'confirming' %}
|
23
|
+
<span class="material-icons-outlined text-sm mr-1 animate-spin">sync</span>
|
24
|
+
{% elif payment.status == 'confirmed' %}
|
25
|
+
<span class="material-icons-outlined text-sm mr-1">check_circle</span>
|
26
|
+
{% elif payment.status == 'completed' %}
|
27
|
+
<span class="material-icons-outlined text-sm mr-1">verified</span>
|
28
|
+
{% elif payment.status == 'failed' %}
|
29
|
+
<span class="material-icons-outlined text-sm mr-1">error</span>
|
30
|
+
{% elif payment.status == 'expired' %}
|
31
|
+
<span class="material-icons-outlined text-sm mr-1">schedule</span>
|
32
|
+
{% elif payment.status == 'cancelled' %}
|
33
|
+
<span class="material-icons-outlined text-sm mr-1">cancel</span>
|
34
|
+
{% elif payment.status == 'refunded' %}
|
35
|
+
<span class="material-icons-outlined text-sm mr-1">undo</span>
|
36
|
+
{% else %}
|
37
|
+
<span class="material-icons-outlined text-sm mr-1">help</span>
|
38
|
+
{% endif %}
|
39
|
+
|
40
|
+
<span class="status-text">{{ payment.get_status_display }}</span>
|
41
|
+
</span>
|
@@ -0,0 +1,83 @@
|
|
1
|
+
{% comment %}
|
2
|
+
Status Overview Component
|
3
|
+
|
4
|
+
Usage:
|
5
|
+
{% include 'payments/components/status_overview.html' with payment_stats=payment_stats %}
|
6
|
+
|
7
|
+
Parameters:
|
8
|
+
- payment_stats: Payment statistics object with counts and totals
|
9
|
+
{% endcomment %}
|
10
|
+
|
11
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
12
|
+
<!-- Total Payments -->
|
13
|
+
{% include 'payments/components/status_card.html' with title="Total Payments" value=payment_stats.total_payments_count|default:0 icon="payments" status="info" %}
|
14
|
+
|
15
|
+
<!-- Pending Payments -->
|
16
|
+
{% include 'payments/components/status_card.html' with title="Pending" value=payment_stats.pending_payments_count|default:0 icon="pending" status="warning" description=payment_stats.confirming_payments_count|add:" confirming"|default:"" %}
|
17
|
+
|
18
|
+
<!-- Completed Payments -->
|
19
|
+
{% include 'payments/components/status_card.html' with title="Completed" value=payment_stats.completed_payments_count|default:0 icon="check_circle" status="success" description=payment_stats.success_rate|floatformat:1|add:"% success rate"|default:"" %}
|
20
|
+
|
21
|
+
<!-- Total Volume -->
|
22
|
+
{% include 'payments/components/status_card.html' with title="Total Volume" value="$"|add:payment_stats.total_volume|floatformat:2|default:"0.00" icon="account_balance" status="info" description=payment_stats.failed_payments_count|add:" failed"|default:"" %}
|
23
|
+
</div>
|
24
|
+
|
25
|
+
<!-- Quick Actions Bar -->
|
26
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 p-4 mt-6">
|
27
|
+
<div class="flex items-center justify-between">
|
28
|
+
<div class="flex items-center space-x-4">
|
29
|
+
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Quick Actions:</span>
|
30
|
+
<div class="flex space-x-2">
|
31
|
+
<a href="{% url 'cfg_payments_admin:payment-form' %}"
|
32
|
+
class="inline-flex items-center px-3 py-1.5 text-xs bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 rounded-md transition-colors">
|
33
|
+
<span class="material-icons-outlined text-sm mr-1">add</span>
|
34
|
+
New Payment
|
35
|
+
</a>
|
36
|
+
<button onclick="window.location.reload()"
|
37
|
+
class="inline-flex items-center px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors">
|
38
|
+
<span class="material-icons-outlined text-sm mr-1">refresh</span>
|
39
|
+
Refresh
|
40
|
+
</button>
|
41
|
+
<button onclick="exportPayments()"
|
42
|
+
class="inline-flex items-center px-3 py-1.5 text-xs border border-gray-300 dark:border-gray-500 text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 rounded-md transition-colors">
|
43
|
+
<span class="material-icons-outlined text-sm mr-1">download</span>
|
44
|
+
Export
|
45
|
+
</button>
|
46
|
+
</div>
|
47
|
+
</div>
|
48
|
+
|
49
|
+
<!-- Live Status Indicator -->
|
50
|
+
<div class="flex items-center space-x-2">
|
51
|
+
<div class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400">
|
52
|
+
<div class="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse"></div>
|
53
|
+
Live Updates
|
54
|
+
</div>
|
55
|
+
<span class="text-xs text-gray-500 dark:text-gray-400">
|
56
|
+
Last update: <span id="stats-last-update">{% now "H:i:s" %}</span>
|
57
|
+
</span>
|
58
|
+
</div>
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<script>
|
63
|
+
// Export payments functionality
|
64
|
+
function exportPayments() {
|
65
|
+
// TODO: Implement export functionality
|
66
|
+
alert('Export functionality not implemented yet');
|
67
|
+
}
|
68
|
+
|
69
|
+
// Update timestamp every minute
|
70
|
+
setInterval(function() {
|
71
|
+
const now = new Date();
|
72
|
+
const timeString = now.toLocaleTimeString('en-US', {
|
73
|
+
hour12: false,
|
74
|
+
hour: '2-digit',
|
75
|
+
minute: '2-digit',
|
76
|
+
second: '2-digit'
|
77
|
+
});
|
78
|
+
const element = document.getElementById('stats-last-update');
|
79
|
+
if (element) {
|
80
|
+
element.textContent = timeString;
|
81
|
+
}
|
82
|
+
}, 60000);
|
83
|
+
</script>
|