django-cfg 1.2.7__py3-none-any.whl → 1.2.8__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/urls.py +2 -2
- django_cfg/modules/django_unfold/callbacks/__init__.py +9 -0
- django_cfg/modules/django_unfold/callbacks/actions.py +50 -0
- django_cfg/modules/django_unfold/callbacks/base.py +98 -0
- django_cfg/modules/django_unfold/callbacks/charts.py +224 -0
- django_cfg/modules/django_unfold/callbacks/commands.py +40 -0
- django_cfg/modules/django_unfold/callbacks/main.py +191 -0
- django_cfg/modules/django_unfold/callbacks/revolution.py +76 -0
- django_cfg/modules/django_unfold/callbacks/statistics.py +240 -0
- django_cfg/modules/django_unfold/callbacks/system.py +180 -0
- django_cfg/modules/django_unfold/callbacks/users.py +65 -0
- django_cfg/modules/django_unfold/models/config.py +10 -3
- django_cfg/modules/django_unfold/tailwind.py +68 -0
- django_cfg/templates/admin/components/action_grid.html +49 -0
- django_cfg/templates/admin/components/card.html +50 -0
- django_cfg/templates/admin/components/data_table.html +67 -0
- django_cfg/templates/admin/components/metric_card.html +39 -0
- django_cfg/templates/admin/components/modal.html +58 -0
- django_cfg/templates/admin/components/progress_bar.html +25 -0
- django_cfg/templates/admin/components/section_header.html +26 -0
- django_cfg/templates/admin/components/stat_item.html +32 -0
- django_cfg/templates/admin/components/stats_grid.html +72 -0
- django_cfg/templates/admin/components/status_badge.html +28 -0
- django_cfg/templates/admin/components/user_avatar.html +27 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +7 -7
- django_cfg/templates/admin/snippets/components/activity_tracker.html +48 -11
- django_cfg/templates/admin/snippets/components/charts_section.html +63 -13
- django_cfg/templates/admin/snippets/components/django_commands.html +18 -18
- django_cfg/templates/admin/snippets/components/quick_actions.html +3 -47
- django_cfg/templates/admin/snippets/components/recent_activity.html +28 -38
- django_cfg/templates/admin/snippets/components/recent_users_table.html +22 -53
- django_cfg/templates/admin/snippets/components/stats_cards.html +2 -66
- django_cfg/templates/admin/snippets/components/system_health.html +13 -63
- django_cfg/templates/admin/snippets/components/system_metrics.html +8 -25
- django_cfg/templates/admin/snippets/tabs/commands_tab.html +1 -1
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +4 -4
- django_cfg/templates/admin/snippets/zones/zones_table.html +12 -33
- django_cfg/templatetags/django_cfg.py +2 -1
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/METADATA +2 -1
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/RECORD +44 -24
- django_cfg/modules/django_unfold/callbacks.py +0 -795
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/WHEEL +0 -0
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.2.7.dist-info → django_cfg-1.2.8.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Action Grid Component -->
|
4
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-{{ cols|default:'3' }} xl:grid-cols-{{ xl_cols|default:'4' }} gap-4">
|
5
|
+
{% for action in actions %}
|
6
|
+
<a href="{{ action.link }}"
|
7
|
+
class="group flex items-center p-4 bg-white dark:bg-base-900 rounded-lg border border-base-200 dark:border-base-700 hover:border-primary-600 dark:hover:border-primary-500 hover:shadow-md transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2">
|
8
|
+
|
9
|
+
<!-- Icon -->
|
10
|
+
<div class="flex-shrink-0 mr-4">
|
11
|
+
<div class="p-2 rounded-lg transition-colors duration-200
|
12
|
+
{% if action.color == 'primary' %}bg-primary-100 dark:bg-primary-900/20 group-hover:bg-primary-200 dark:group-hover:bg-primary-800/30
|
13
|
+
{% elif action.color == 'success' %}bg-green-100 dark:bg-green-900/20 group-hover:bg-green-200 dark:group-hover:bg-green-800/30
|
14
|
+
{% elif action.color == 'warning' %}bg-amber-100 dark:bg-amber-900/20 group-hover:bg-amber-200 dark:group-hover:bg-amber-800/30
|
15
|
+
{% elif action.color == 'danger' %}bg-red-100 dark:bg-red-900/20 group-hover:bg-red-200 dark:group-hover:bg-red-800/30
|
16
|
+
{% else %}bg-base-100 dark:bg-base-800 group-hover:bg-base-200 dark:group-hover:bg-base-700{% endif %}">
|
17
|
+
|
18
|
+
<span class="material-icons text-lg
|
19
|
+
{% if action.color == 'primary' %}text-primary-600 dark:text-primary-400
|
20
|
+
{% elif action.color == 'success' %}text-green-600 dark:text-green-400
|
21
|
+
{% elif action.color == 'warning' %}text-amber-600 dark:text-amber-400
|
22
|
+
{% elif action.color == 'danger' %}text-red-600 dark:text-red-400
|
23
|
+
{% else %}text-font-subtle-light dark:text-font-subtle-dark{% endif %}">
|
24
|
+
{{ action.icon }}
|
25
|
+
</span>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<!-- Content -->
|
30
|
+
<div class="flex-1 min-w-0">
|
31
|
+
<h3 class="text-sm font-medium text-font-default-light dark:text-font-default-dark group-hover:text-primary-600 dark:group-hover:text-primary-500 transition-colors duration-200">
|
32
|
+
{{ action.title }}
|
33
|
+
</h3>
|
34
|
+
{% if action.description %}
|
35
|
+
<p class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-1 line-clamp-2">
|
36
|
+
{{ action.description }}
|
37
|
+
</p>
|
38
|
+
{% endif %}
|
39
|
+
</div>
|
40
|
+
|
41
|
+
<!-- Arrow -->
|
42
|
+
<div class="flex-shrink-0 ml-2">
|
43
|
+
<span class="material-icons text-base-400 dark:text-base-500 group-hover:text-primary-600 dark:group-hover:text-primary-500 transition-colors duration-200">
|
44
|
+
arrow_forward
|
45
|
+
</span>
|
46
|
+
</div>
|
47
|
+
</a>
|
48
|
+
{% endfor %}
|
49
|
+
</div>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Enhanced Card Component following Unfold patterns -->
|
4
|
+
<div class="bg-white dark:bg-base-900 rounded-lg border border-base-200 dark:border-base-700 shadow-xs overflow-hidden {% if hover %}hover:shadow-md hover:border-base-300 dark:hover:border-base-600 transition-all duration-200{% endif %}{% if class %} {{ class }}{% endif %}">
|
5
|
+
|
6
|
+
<!-- Card Header -->
|
7
|
+
{% if title or header_actions %}
|
8
|
+
<div class="p-6 border-b border-base-200 dark:border-base-700 {% if header_bg %}{{ header_bg }}{% else %}bg-base-50 dark:bg-base-800{% endif %}">
|
9
|
+
<div class="flex items-center justify-between">
|
10
|
+
{% if title %}
|
11
|
+
<div class="flex items-center space-x-3">
|
12
|
+
{% if icon %}
|
13
|
+
<div class="flex items-center justify-center w-8 h-8 bg-{{ icon_color|default:'primary' }}-100 dark:bg-{{ icon_color|default:'primary' }}-900/20 rounded-lg">
|
14
|
+
<span class="material-icons text-{{ icon_color|default:'primary' }}-600 dark:text-{{ icon_color|default:'primary' }}-400 text-lg">{{ icon }}</span>
|
15
|
+
</div>
|
16
|
+
{% endif %}
|
17
|
+
<div>
|
18
|
+
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark">
|
19
|
+
{{ title }}
|
20
|
+
</h3>
|
21
|
+
{% if subtitle %}
|
22
|
+
<p class="text-sm text-font-subtle-light dark:text-font-subtle-dark mt-1">
|
23
|
+
{{ subtitle }}
|
24
|
+
</p>
|
25
|
+
{% endif %}
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
{% endif %}
|
29
|
+
|
30
|
+
{% if header_actions %}
|
31
|
+
<div class="flex items-center space-x-2">
|
32
|
+
{{ header_actions }}
|
33
|
+
</div>
|
34
|
+
{% endif %}
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
{% endif %}
|
38
|
+
|
39
|
+
<!-- Card Body -->
|
40
|
+
<div class="p-6{% if compact %} p-4{% endif %}">
|
41
|
+
{{ content }}
|
42
|
+
</div>
|
43
|
+
|
44
|
+
<!-- Card Footer -->
|
45
|
+
{% if footer %}
|
46
|
+
<div class="px-6 py-4 border-t border-base-200 dark:border-base-700 bg-base-50 dark:bg-base-800">
|
47
|
+
{{ footer }}
|
48
|
+
</div>
|
49
|
+
{% endif %}
|
50
|
+
</div>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Data Table Component -->
|
4
|
+
<div class="bg-white dark:bg-base-900 rounded-lg border border-base-200 dark:border-base-700 overflow-hidden shadow-xs">
|
5
|
+
|
6
|
+
<!-- Table Header -->
|
7
|
+
{% if title or actions %}
|
8
|
+
<div class="px-6 py-4 border-b border-base-200 dark:border-base-700 bg-base-50 dark:bg-base-800">
|
9
|
+
<div class="flex items-center justify-between">
|
10
|
+
{% if title %}
|
11
|
+
<div class="flex items-center">
|
12
|
+
{% if icon %}
|
13
|
+
<span class="material-icons text-primary-600 mr-2">{{ icon }}</span>
|
14
|
+
{% endif %}
|
15
|
+
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark">{{ title }}</h3>
|
16
|
+
</div>
|
17
|
+
{% endif %}
|
18
|
+
|
19
|
+
{% if actions %}
|
20
|
+
<div class="flex items-center space-x-2">
|
21
|
+
{{ actions }}
|
22
|
+
</div>
|
23
|
+
{% endif %}
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
{% endif %}
|
27
|
+
|
28
|
+
<!-- Table Content -->
|
29
|
+
<div class="overflow-x-auto">
|
30
|
+
<table class="w-full">
|
31
|
+
{% if headers %}
|
32
|
+
<thead class="bg-base-50 dark:bg-base-800">
|
33
|
+
<tr>
|
34
|
+
{% for header in headers %}
|
35
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-font-subtle-light dark:text-font-subtle-dark uppercase tracking-wider">
|
36
|
+
{{ header }}
|
37
|
+
</th>
|
38
|
+
{% endfor %}
|
39
|
+
</tr>
|
40
|
+
</thead>
|
41
|
+
{% endif %}
|
42
|
+
|
43
|
+
<tbody class="bg-white dark:bg-base-900 divide-y divide-base-200 dark:divide-base-700">
|
44
|
+
{% if rows %}
|
45
|
+
{% for row in rows %}
|
46
|
+
<tr class="hover:bg-base-100 dark:hover:bg-base-800 transition-colors duration-150">
|
47
|
+
{% for cell in row %}
|
48
|
+
<td class="px-6 py-4 whitespace-nowrap">
|
49
|
+
{{ cell }}
|
50
|
+
</td>
|
51
|
+
{% endfor %}
|
52
|
+
</tr>
|
53
|
+
{% endfor %}
|
54
|
+
{% else %}
|
55
|
+
<tr>
|
56
|
+
<td colspan="{{ headers|length|default:'5' }}" class="px-6 py-8 text-center">
|
57
|
+
<div class="flex flex-col items-center">
|
58
|
+
<span class="material-icons text-4xl text-base-400 dark:text-base-500 mb-2">{{ empty_icon|default:'inbox' }}</span>
|
59
|
+
<p class="text-font-subtle-light dark:text-font-subtle-dark">{{ empty_message|default:'No data available' }}</p>
|
60
|
+
</div>
|
61
|
+
</td>
|
62
|
+
</tr>
|
63
|
+
{% endif %}
|
64
|
+
</tbody>
|
65
|
+
</table>
|
66
|
+
</div>
|
67
|
+
</div>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Metric Card Component -->
|
4
|
+
<div class="bg-white dark:bg-base-900 rounded-lg p-5 border border-base-200 dark:border-base-700 hover:shadow-md transition-shadow duration-200">
|
5
|
+
<div class="flex items-center justify-between">
|
6
|
+
<div class="flex items-center space-x-3">
|
7
|
+
<div class="flex-shrink-0">
|
8
|
+
<span class="material-icons
|
9
|
+
{% if status == 'healthy' or status == 'success' %}text-green-600 dark:text-green-400
|
10
|
+
{% elif status == 'warning' %}text-amber-600 dark:text-amber-400
|
11
|
+
{% elif status == 'error' or status == 'failed' %}text-red-600 dark:text-red-400
|
12
|
+
{% elif status == 'info' %}text-blue-600 dark:text-blue-400
|
13
|
+
{% else %}text-primary-600 dark:text-primary-400{% endif %}">
|
14
|
+
{{ icon }}
|
15
|
+
</span>
|
16
|
+
</div>
|
17
|
+
<div>
|
18
|
+
<div class="text-sm font-medium text-font-default-light dark:text-font-default-dark">
|
19
|
+
{{ title }}
|
20
|
+
</div>
|
21
|
+
{% if description %}
|
22
|
+
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-1">
|
23
|
+
{{ description }}
|
24
|
+
</div>
|
25
|
+
{% endif %}
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div class="flex-shrink-0">
|
30
|
+
{% include 'admin/components/status_badge.html' with status=status text=status_text %}
|
31
|
+
</div>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
{% if progress %}
|
35
|
+
<div class="mt-4">
|
36
|
+
{% include 'admin/components/progress_bar.html' with value=progress title=progress_title description=progress_description %}
|
37
|
+
</div>
|
38
|
+
{% endif %}
|
39
|
+
</div>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Reusable Modal Component based on Unfold patterns -->
|
4
|
+
<div id="{{ modal_id|default:'modal' }}" class="fixed inset-0 bg-black/80 backdrop-blur-sm hidden z-50">
|
5
|
+
<div class="flex items-center justify-center min-h-screen p-4">
|
6
|
+
<div class="bg-white dark:bg-base-900 rounded-lg shadow-2xl max-w-{{ max_width|default:'4xl' }} w-full max-h-[80vh] flex flex-col border border-base-200 dark:border-base-700">
|
7
|
+
|
8
|
+
<!-- Modal Header -->
|
9
|
+
{% if title or closable %}
|
10
|
+
<div class="flex items-center justify-between p-4 border-b border-base-200 dark:border-base-700 bg-base-50 dark:bg-base-800">
|
11
|
+
{% if title %}
|
12
|
+
<div class="flex items-center space-x-3">
|
13
|
+
{% if icon %}
|
14
|
+
<div class="flex items-center justify-center w-10 h-10 bg-primary-100 dark:bg-primary-900/20 rounded-lg">
|
15
|
+
<span class="material-icons text-primary-600 dark:text-primary-400">{{ icon }}</span>
|
16
|
+
</div>
|
17
|
+
{% endif %}
|
18
|
+
<div>
|
19
|
+
<h3 class="text-lg font-semibold text-font-important-light dark:text-font-important-dark">
|
20
|
+
{{ title }}
|
21
|
+
</h3>
|
22
|
+
{% if subtitle %}
|
23
|
+
<p class="text-sm text-font-subtle-light dark:text-font-subtle-dark">
|
24
|
+
{{ subtitle }}
|
25
|
+
</p>
|
26
|
+
{% endif %}
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
{% endif %}
|
30
|
+
|
31
|
+
{% if closable %}
|
32
|
+
<button onclick="{{ close_function|default:'closeModal()' }}" class="p-2 text-font-subtle-light dark:text-font-subtle-dark hover:text-font-default-light dark:hover:text-font-default-dark hover:bg-base-100 dark:hover:bg-base-700 rounded-lg transition-colors">
|
33
|
+
<span class="material-icons">close</span>
|
34
|
+
</button>
|
35
|
+
{% endif %}
|
36
|
+
</div>
|
37
|
+
{% endif %}
|
38
|
+
|
39
|
+
<!-- Modal Body -->
|
40
|
+
<div class="flex-1 overflow-hidden p-4">
|
41
|
+
{% if scrollable %}
|
42
|
+
<div class="bg-base-100 dark:bg-base-800 rounded-lg border border-base-200 dark:border-base-700 h-full overflow-y-auto p-4">
|
43
|
+
{{ content }}
|
44
|
+
</div>
|
45
|
+
{% else %}
|
46
|
+
{{ content }}
|
47
|
+
{% endif %}
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<!-- Modal Footer -->
|
51
|
+
{% if footer %}
|
52
|
+
<div class="flex items-center justify-between p-4 border-t border-base-200 dark:border-base-700 bg-base-50 dark:bg-base-800">
|
53
|
+
{{ footer }}
|
54
|
+
</div>
|
55
|
+
{% endif %}
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
</div>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Progress Bar Component -->
|
4
|
+
<div class="w-full">
|
5
|
+
{% if title %}
|
6
|
+
<div class="flex items-center justify-between mb-2">
|
7
|
+
<span class="text-sm font-medium text-font-default-light dark:text-font-default-dark">{{ title }}</span>
|
8
|
+
<span class="text-sm text-font-subtle-light dark:text-font-subtle-dark">{{ value }}%</span>
|
9
|
+
</div>
|
10
|
+
{% endif %}
|
11
|
+
|
12
|
+
<div class="w-full bg-base-200 dark:bg-base-700 rounded-full h-2">
|
13
|
+
<div class="h-2 rounded-full transition-all duration-300
|
14
|
+
{% if value >= 80 %}bg-green-500
|
15
|
+
{% elif value >= 60 %}bg-amber-500
|
16
|
+
{% elif value >= 40 %}bg-orange-500
|
17
|
+
{% else %}bg-red-500{% endif %}"
|
18
|
+
style="width: {{ value|default:0 }}%">
|
19
|
+
</div>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
{% if description %}
|
23
|
+
<p class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-1">{{ description }}</p>
|
24
|
+
{% endif %}
|
25
|
+
</div>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Section Header Component -->
|
4
|
+
<div class="flex items-center mb-6">
|
5
|
+
{% if icon %}
|
6
|
+
<div class="flex items-center justify-center w-8 h-8 bg-{{ icon_color|default:'primary' }}-100 dark:bg-{{ icon_color|default:'primary' }}-900/20 rounded-lg mr-3">
|
7
|
+
<span class="material-icons text-{{ icon_color|default:'primary' }}-600 dark:text-{{ icon_color|default:'primary' }}-400 text-lg">{{ icon }}</span>
|
8
|
+
</div>
|
9
|
+
{% endif %}
|
10
|
+
|
11
|
+
<h2 class="text-xl font-semibold text-font-important-light dark:text-font-important-dark">
|
12
|
+
{{ title }}
|
13
|
+
</h2>
|
14
|
+
|
15
|
+
{% if badge %}
|
16
|
+
<span class="ml-auto text-xs text-font-subtle-light dark:text-font-subtle-dark bg-base-100 dark:bg-base-800 px-2 py-1 rounded-full">
|
17
|
+
{{ badge }}
|
18
|
+
</span>
|
19
|
+
{% endif %}
|
20
|
+
|
21
|
+
{% if actions %}
|
22
|
+
<div class="ml-auto flex items-center space-x-2">
|
23
|
+
{{ actions }}
|
24
|
+
</div>
|
25
|
+
{% endif %}
|
26
|
+
</div>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Stat Item Component -->
|
4
|
+
<div class="flex items-center justify-between p-3 bg-base-50 dark:bg-base-800 rounded-lg hover:bg-base-100 dark:hover:bg-base-700 transition-colors duration-150 group">
|
5
|
+
<div class="flex items-center">
|
6
|
+
{% if icon %}
|
7
|
+
<div class="w-8 h-8 rounded-lg flex items-center justify-center mr-3 bg-{{ color|default:'primary' }}-100 dark:bg-{{ color|default:'primary' }}-900/20">
|
8
|
+
<span class="material-icons text-xs text-{{ color|default:'primary' }}-600 dark:text-{{ color|default:'primary' }}-400">{{ icon }}</span>
|
9
|
+
</div>
|
10
|
+
{% endif %}
|
11
|
+
<div>
|
12
|
+
<div class="text-sm font-medium text-font-default-light dark:text-font-default-dark">{{ title }}</div>
|
13
|
+
{% if description %}
|
14
|
+
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark">{{ description }}</div>
|
15
|
+
{% endif %}
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="flex items-center gap-3">
|
20
|
+
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-{{ color|default:'primary' }}-100 dark:bg-{{ color|default:'primary' }}-900/20 text-{{ color|default:'primary' }}-600 dark:text-{{ color|default:'primary' }}-400">
|
21
|
+
{{ count|default:0 }}
|
22
|
+
</span>
|
23
|
+
|
24
|
+
{% if url %}
|
25
|
+
<a href="{{ url }}"
|
26
|
+
class="inline-flex items-center gap-1 px-3 py-1 bg-primary-600 hover:bg-primary-700 dark:bg-primary-500 dark:hover:bg-primary-600 text-white rounded-lg text-xs font-medium transition-colors duration-150 group-hover:shadow-sm">
|
27
|
+
<span class="material-icons text-xs">visibility</span>
|
28
|
+
<span>View</span>
|
29
|
+
</a>
|
30
|
+
{% endif %}
|
31
|
+
</div>
|
32
|
+
</div>
|
@@ -0,0 +1,72 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Statistics Grid Component -->
|
4
|
+
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-{{ cols|default:'4' }} gap-{{ gap|default:'4' }} {% if class %}{{ class }}{% endif %}">
|
5
|
+
{% for stat in stats %}
|
6
|
+
<div class="bg-white dark:bg-base-900 rounded-lg shadow-sm hover:shadow-md transition-all duration-300 border border-base-200 dark:border-base-700 overflow-hidden group">
|
7
|
+
<div class="p-5">
|
8
|
+
<div class="flex items-start justify-between">
|
9
|
+
<div class="flex-1">
|
10
|
+
<!-- Title -->
|
11
|
+
<div class="text-xs font-semibold text-font-subtle-light dark:text-font-subtle-dark uppercase tracking-wider mb-2">
|
12
|
+
{{ stat.title }}
|
13
|
+
</div>
|
14
|
+
|
15
|
+
<!-- Value -->
|
16
|
+
<div class="text-2xl font-bold text-font-important-light dark:text-font-important-dark mb-1">
|
17
|
+
{{ stat.value }}
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<!-- Change indicator -->
|
21
|
+
{% if stat.change %}
|
22
|
+
<div class="flex items-center">
|
23
|
+
<div class="flex items-center px-2 py-1 rounded-full text-xs font-medium
|
24
|
+
{% if stat.change_type == 'positive' %}bg-green-100 dark:bg-green-900/20 text-green-600 dark:text-green-400
|
25
|
+
{% elif stat.change_type == 'negative' %}bg-red-100 dark:bg-red-900/20 text-red-600 dark:text-red-400
|
26
|
+
{% else %}bg-base-100 dark:bg-base-800 text-font-default-light dark:text-font-default-dark{% endif %}">
|
27
|
+
|
28
|
+
<span class="material-icons text-xs mr-1">
|
29
|
+
{% if stat.change_type == 'positive' %}arrow_upward
|
30
|
+
{% elif stat.change_type == 'negative' %}arrow_downward
|
31
|
+
{% else %}remove{% endif %}
|
32
|
+
</span>
|
33
|
+
{{ stat.change }}
|
34
|
+
</div>
|
35
|
+
</div>
|
36
|
+
{% endif %}
|
37
|
+
|
38
|
+
<!-- Description -->
|
39
|
+
{% if stat.description %}
|
40
|
+
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark mt-1">
|
41
|
+
{{ stat.description }}
|
42
|
+
</div>
|
43
|
+
{% endif %}
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<!-- Icon -->
|
47
|
+
{% if stat.icon %}
|
48
|
+
<div class="flex-shrink-0 ml-3">
|
49
|
+
<div class="w-10 h-10 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
|
50
|
+
<span class="material-icons text-2xl
|
51
|
+
{% if stat.change_type == 'positive' %}text-green-600 dark:text-green-400
|
52
|
+
{% elif stat.change_type == 'negative' %}text-red-600 dark:text-red-400
|
53
|
+
{% else %}text-primary-600 dark:text-primary-400{% endif %}">
|
54
|
+
{{ stat.icon }}
|
55
|
+
</span>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
{% endif %}
|
59
|
+
</div>
|
60
|
+
</div>
|
61
|
+
|
62
|
+
<!-- Accent bar -->
|
63
|
+
{% if stat.show_accent %}
|
64
|
+
<div class="h-1 w-full
|
65
|
+
{% if stat.change_type == 'positive' %}bg-gradient-to-r from-green-400 to-green-600
|
66
|
+
{% elif stat.change_type == 'negative' %}bg-gradient-to-r from-red-400 to-red-600
|
67
|
+
{% else %}bg-gradient-to-r from-primary-400 to-primary-600{% endif %}">
|
68
|
+
</div>
|
69
|
+
{% endif %}
|
70
|
+
</div>
|
71
|
+
{% endfor %}
|
72
|
+
</div>
|
@@ -0,0 +1,28 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- Status Badge Component -->
|
4
|
+
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
|
5
|
+
{% if status == 'active' or status == 'healthy' or status == 'success' %}bg-green-100 dark:bg-green-900/20 text-green-600 dark:text-green-400
|
6
|
+
{% elif status == 'warning' or status == 'pending' %}bg-amber-100 dark:bg-amber-900/20 text-amber-600 dark:text-amber-400
|
7
|
+
{% elif status == 'error' or status == 'failed' or status == 'inactive' %}bg-red-100 dark:bg-red-900/20 text-red-600 dark:text-red-400
|
8
|
+
{% elif status == 'info' or status == 'processing' %}bg-blue-100 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400
|
9
|
+
{% else %}bg-base-100 dark:bg-base-800 text-font-subtle-light dark:text-font-subtle-dark{% endif %}">
|
10
|
+
|
11
|
+
{% if icon %}
|
12
|
+
<span class="material-icons text-xs mr-1">{{ icon }}</span>
|
13
|
+
{% else %}
|
14
|
+
{% if status == 'active' or status == 'healthy' or status == 'success' %}
|
15
|
+
<span class="material-icons text-xs mr-1">check_circle</span>
|
16
|
+
{% elif status == 'warning' or status == 'pending' %}
|
17
|
+
<span class="material-icons text-xs mr-1">warning</span>
|
18
|
+
{% elif status == 'error' or status == 'failed' %}
|
19
|
+
<span class="material-icons text-xs mr-1">error</span>
|
20
|
+
{% elif status == 'inactive' %}
|
21
|
+
<span class="material-icons text-xs mr-1">cancel</span>
|
22
|
+
{% elif status == 'info' or status == 'processing' %}
|
23
|
+
<span class="material-icons text-xs mr-1">info</span>
|
24
|
+
{% endif %}
|
25
|
+
{% endif %}
|
26
|
+
|
27
|
+
{{ text|default:status|title }}
|
28
|
+
</span>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
{% load unfold %}
|
2
|
+
|
3
|
+
<!-- User Avatar Component -->
|
4
|
+
<div class="flex items-center">
|
5
|
+
<div class="h-{{ size|default:'8' }} w-{{ size|default:'8' }} bg-{{ color|default:'primary' }}-100 dark:bg-{{ color|default:'primary' }}-900/20 rounded-full flex items-center justify-center mr-3">
|
6
|
+
{% if image %}
|
7
|
+
<img src="{{ image }}" alt="{{ name }}" class="h-{{ size|default:'8' }} w-{{ size|default:'8' }} rounded-full object-cover">
|
8
|
+
{% else %}
|
9
|
+
<span class="text-sm font-medium text-{{ color|default:'primary' }}-600 dark:text-{{ color|default:'primary' }}-400">
|
10
|
+
{% if name %}{{ name|slice:":1"|upper }}{% elif email %}{{ email|slice:":1"|upper }}{% else %}?{% endif %}
|
11
|
+
</span>
|
12
|
+
{% endif %}
|
13
|
+
</div>
|
14
|
+
|
15
|
+
{% if show_details %}
|
16
|
+
<div>
|
17
|
+
<div class="text-sm font-medium text-font-default-light dark:text-font-default-dark">
|
18
|
+
{% if name %}{{ name|truncatechars:20 }}{% elif email %}{{ email|truncatechars:20 }}{% else %}Unknown User{% endif %}
|
19
|
+
</div>
|
20
|
+
{% if email and name %}
|
21
|
+
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark">
|
22
|
+
{{ email|truncatechars:30 }}
|
23
|
+
</div>
|
24
|
+
{% endif %}
|
25
|
+
</div>
|
26
|
+
{% endif %}
|
27
|
+
</div>
|
@@ -246,13 +246,13 @@
|
|
246
246
|
}
|
247
247
|
|
248
248
|
function scrollToBottom(element) {
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
}
|
249
|
+
if (!element) return;
|
250
|
+
|
251
|
+
// Принудительно скроллим элемент
|
252
|
+
setTimeout(() => {
|
253
|
+
element.scrollTop = element.scrollHeight;
|
254
|
+
console.log('Scrolled to bottom:', element.scrollTop, element.scrollHeight);
|
255
|
+
}, 50);
|
256
256
|
}
|
257
257
|
|
258
258
|
window.closeCommandModal = function() {
|
@@ -1,17 +1,54 @@
|
|
1
1
|
{% load unfold %}
|
2
2
|
|
3
|
-
<!-- Activity Tracker -->
|
3
|
+
<!-- Activity Tracker using reusable components -->
|
4
4
|
<div class="mt-8 w-full">
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
{% include 'admin/components/section_header.html' with title='Activity Tracker' icon='timeline' icon_color='primary' %}
|
6
|
+
|
7
|
+
{% component "unfold/components/card.html" with title="User Activity Heatmap" subtitle="Last 52 weeks of user activity" %}
|
8
|
+
<div class="space-y-4">
|
9
|
+
<!-- Legend -->
|
10
|
+
<div class="flex items-center justify-between text-sm text-font-subtle-light dark:text-font-subtle-dark">
|
11
|
+
<span>Less</span>
|
12
|
+
<div class="flex items-center space-x-1">
|
13
|
+
<div class="w-3 h-3 bg-base-200 dark:bg-base-700 rounded-sm"></div>
|
14
|
+
<div class="w-3 h-3 bg-green-200 dark:bg-green-800 rounded-sm"></div>
|
15
|
+
<div class="w-3 h-3 bg-green-400 dark:bg-green-600 rounded-sm"></div>
|
16
|
+
<div class="w-3 h-3 bg-green-600 dark:bg-green-500 rounded-sm"></div>
|
17
|
+
<div class="w-3 h-3 bg-green-800 dark:bg-green-400 rounded-sm"></div>
|
18
|
+
</div>
|
19
|
+
<span>More</span>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<!-- Activity Tracker Grid -->
|
23
|
+
<div class="overflow-x-auto">
|
24
|
+
{% if activity_tracker %}
|
25
|
+
{% component "unfold/components/tracker.html" with data=activity_tracker %}
|
26
|
+
{% endcomponent %}
|
27
|
+
{% else %}
|
28
|
+
<div class="flex items-center justify-center p-8 bg-base-50 dark:bg-base-800 rounded-lg border border-base-200 dark:border-base-700">
|
29
|
+
<div class="text-center">
|
30
|
+
<span class="material-icons text-4xl text-base-400 dark:text-base-500 mb-2">timeline</span>
|
31
|
+
<p class="text-font-subtle-light dark:text-font-subtle-dark">No activity data available</p>
|
32
|
+
</div>
|
33
|
+
</div>
|
34
|
+
{% endif %}
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<!-- Summary Stats -->
|
38
|
+
<div class="grid grid-cols-3 gap-4 pt-4 border-t border-base-200 dark:border-base-700">
|
39
|
+
<div class="text-center">
|
40
|
+
<div class="text-lg font-semibold text-font-default-light dark:text-font-default-dark">365</div>
|
41
|
+
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark">Days tracked</div>
|
42
|
+
</div>
|
43
|
+
<div class="text-center">
|
44
|
+
<div class="text-lg font-semibold text-green-600 dark:text-green-400">52</div>
|
45
|
+
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark">Weeks</div>
|
46
|
+
</div>
|
47
|
+
<div class="text-center">
|
48
|
+
<div class="text-lg font-semibold text-primary-600 dark:text-primary-400">{{ activity_tracker|length|default:"0" }}</div>
|
49
|
+
<div class="text-xs text-font-subtle-light dark:text-font-subtle-dark">Data points</div>
|
50
|
+
</div>
|
51
|
+
</div>
|
8
52
|
</div>
|
9
|
-
<h2 class="text-xl font-semibold text-font-important-light dark:text-font-important-dark">
|
10
|
-
Activity Tracker
|
11
|
-
</h2>
|
12
|
-
</div>
|
13
|
-
{% component "unfold/components/card.html" %}
|
14
|
-
{% component "unfold/components/tracker.html" with data=activity_tracker %}
|
15
|
-
{% endcomponent %}
|
16
53
|
{% endcomponent %}
|
17
54
|
</div>
|