diff --git a/app/templates/shared/macros/cards.html b/app/templates/shared/macros/cards.html
index 758f0b90..a321b634 100644
--- a/app/templates/shared/macros/cards.html
+++ b/app/templates/shared/macros/cards.html
@@ -49,6 +49,65 @@
{% endmacro %}
+{#
+ Stat Card with Trend
+ ====================
+ A statistics card with icon, value, and trend indicator.
+
+ Parameters:
+ - icon: Icon name
+ - label: Card label/title
+ - value: Alpine.js expression for the value
+ - trend_value: Alpine.js expression for trend percentage (e.g., '+12.5%' or '-3.2%')
+ - trend_direction: Alpine.js expression for direction ('up' | 'down' | 'neutral')
+ - color: Card color theme
+ - format: Optional format function
+ - compare_label: Label for comparison period (default: 'vs last month')
+#}
+{% macro stat_card_with_trend(icon, label, value, trend_value, trend_direction='neutral', color='orange', format=none, compare_label='vs last month') %}
+{% set colors = {
+ 'orange': 'text-orange-500 bg-orange-100 dark:text-orange-100 dark:bg-orange-500',
+ 'green': 'text-green-500 bg-green-100 dark:text-green-100 dark:bg-green-500',
+ 'blue': 'text-blue-500 bg-blue-100 dark:text-blue-100 dark:bg-blue-500',
+ 'purple': 'text-purple-500 bg-purple-100 dark:text-purple-100 dark:bg-purple-500',
+ 'red': 'text-red-500 bg-red-100 dark:text-red-100 dark:bg-red-500',
+ 'yellow': 'text-yellow-500 bg-yellow-100 dark:text-yellow-100 dark:bg-yellow-500',
+ 'teal': 'text-teal-500 bg-teal-100 dark:text-teal-100 dark:bg-teal-500'
+} %}
+
+
+
+
+
+
+
+
+
+
{{ compare_label }}
+
+
+
+{% endmacro %}
+
+
{#
Stats Grid
==========
@@ -97,6 +156,110 @@
{% endmacro %}
+{#
+ Card with Menu
+ ==============
+ A card with a dropdown menu in the header.
+
+ Parameters:
+ - title: Card title
+ - subtitle: Card subtitle (optional)
+ - menu_var: Alpine.js variable for menu open state (default: 'menuOpen')
+ - class_extra: Additional CSS classes
+#}
+{% macro card_with_menu(title, subtitle=none, menu_var='menuOpen', class_extra='') %}
+
+
+
+
{{ title }}
+ {% if subtitle %}
+
{{ subtitle }}
+ {% endif %}
+
+
+
+
+ {{ caller_menu() if caller_menu is defined else '' }}
+
+
+
+
+ {{ caller() }}
+
+
+{% endmacro %}
+
+
+{#
+ Card with Menu (Simple)
+ =======================
+ A simpler card with menu that accepts menu items as a parameter.
+
+ Parameters:
+ - title: Card title
+ - menu_items: List of menu items [{'label': '', 'onclick': '', 'icon': '', 'variant': ''}]
+ - subtitle: Card subtitle (optional)
+ - class_extra: Additional CSS classes
+#}
+{% macro card_with_menu_simple(title, menu_items=[], subtitle=none, class_extra='') %}
+
+
+
+
{{ title }}
+ {% if subtitle %}
+
{{ subtitle }}
+ {% endif %}
+
+ {% if menu_items %}
+
+
+
+ {% for item in menu_items %}
+
+ {% endfor %}
+
+
+ {% endif %}
+
+
+ {{ caller() }}
+
+
+{% endmacro %}
+
+
{#
Info Card
=========
diff --git a/app/templates/shared/macros/forms.html b/app/templates/shared/macros/forms.html
index 2cbe6edf..564a536d 100644
--- a/app/templates/shared/macros/forms.html
+++ b/app/templates/shared/macros/forms.html
@@ -544,3 +544,227 @@
{% endmacro %}
+
+
+{#
+ Searchable Select
+ =================
+ A select dropdown with search/filter functionality.
+
+ Parameters:
+ - label: Field label
+ - x_model: Alpine.js x-model binding for selected value
+ - options_var: Alpine.js variable containing options array
+ - value_key: Key for option value (default: 'value')
+ - label_key: Key for option label (default: 'label')
+ - search_var: Alpine.js variable for search query (default: 'searchQuery')
+ - open_var: Alpine.js variable for dropdown open state (default: 'isOpen')
+ - placeholder: Placeholder text (default: 'Select...')
+ - search_placeholder: Search input placeholder (default: 'Search...')
+ - required: Whether the field is required
+ - disabled: Alpine.js expression for disabled state
+ - no_results_text: Text shown when no results (default: 'No results found')
+
+ Usage:
+
+ {{ searchable_select('Category', 'selected', 'filteredOptions', value_key='id', label_key='name') }}
+
+#}
+{% macro searchable_select(label, x_model, options_var, value_key='value', label_key='label', search_var='searchQuery', open_var='isOpen', placeholder='Select...', search_placeholder='Search...', required=false, disabled=none, no_results_text='No results found') %}
+
+
+
+{% endmacro %}
+
+
+{#
+ Multi-Select with Tags
+ ======================
+ A multi-select dropdown that shows selected items as tags.
+
+ Parameters:
+ - label: Field label
+ - selected_var: Alpine.js variable for selected values array
+ - options_var: Alpine.js variable containing options array
+ - value_key: Key for option value (default: 'value')
+ - label_key: Key for option label (default: 'label')
+ - placeholder: Placeholder text (default: 'Select items...')
+ - max_items: Maximum number of items (optional)
+
+ Required Alpine.js methods:
+ toggleOption(option) - Add/remove option from selection
+ removeOption(value) - Remove option by value
+ isSelected(value) - Check if option is selected
+ getLabel(value) - Get label for a value
+#}
+{% macro multi_select(label, selected_var, options_var, value_key='value', label_key='label', placeholder='Select items...', max_items=none) %}
+
+
+ {% if max_items %}
+
+ / {{ max_items }} selected
+
+ {% endif %}
+
+{% endmacro %}
diff --git a/app/templates/shared/macros/tables.html b/app/templates/shared/macros/tables.html
index fdba39a6..0af93422 100644
--- a/app/templates/shared/macros/tables.html
+++ b/app/templates/shared/macros/tables.html
@@ -48,6 +48,76 @@
{% endmacro %}
+{#
+ Sortable Table Header
+ =====================
+ Renders a table header with sortable columns.
+
+ Parameters:
+ - columns: List of column definitions [{'label': '', 'key': '', 'sortable': true/false, 'width': ''}]
+ - sort_key_var: Alpine.js variable for current sort key (default: 'sortKey')
+ - sort_dir_var: Alpine.js variable for sort direction (default: 'sortDir')
+ - on_sort: Alpine.js handler when column is clicked (default: 'sortBy')
+
+ Usage:
+ {{ sortable_table_header([
+ {'label': 'Name', 'key': 'name', 'sortable': true},
+ {'label': 'Email', 'key': 'email', 'sortable': true},
+ {'label': 'Status', 'key': 'status', 'sortable': false},
+ {'label': 'Actions', 'key': 'actions', 'sortable': false, 'width': 'w-24'}
+ ]) }}
+
+ Required Alpine.js:
+ sortKey: 'name',
+ sortDir: 'asc',
+ sortBy(key) {
+ if (this.sortKey === key) {
+ this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
+ } else {
+ this.sortKey = key;
+ this.sortDir = 'asc';
+ }
+ this.loadItems();
+ }
+#}
+{% macro sortable_table_header(columns, sort_key_var='sortKey', sort_dir_var='sortDir', on_sort='sortBy') %}
+
+
+ {% for col in columns %}
+ |
+ {% if col.sortable %}
+
+ {% else %}
+ {{ col.label }}
+ {% endif %}
+ |
+ {% endfor %}
+
+
+{% endmacro %}
+
+
{#
Table Body Wrapper
==================