feat(templates/projects): add automation UI and modal for board automation rules

Enable management of automation rules with UI buttons, modal, and status condition handling
This commit is contained in:
sankalp committed 2026-06-29 07:50:09 +05:45
1 parent 84cb41790c
commit 2f1df8ab03
3 files changed
+184 -2

No files matched your search

+6
View File
@@ -36,6 +36,7 @@
filterOpen: false,
manageStatusesOpen: false,
permissionsOpen: false,
automationOpen: false,
allowedMoveToStatusIds: {{ allowed_move_to_status_ids }},
createStatusName: '',
@@ -322,6 +323,9 @@ class="space-y-6"
<button @click="permissionsOpen = true" class="dk-btn dk-btn-ghost text-sm text-orange-600">
<i class="fas fa-shield-halved"></i> Permissions
</button>
<button @click="automationOpen = true" class="dk-btn dk-btn-ghost text-sm text-cyan-600">
<i class="fas fa-robot"></i> Automation
</button>
<button @click="manageStatusesOpen = true" class="dk-btn dk-btn-ghost text-sm text-purple-600">
<i class="fas fa-cog"></i> Manage Statuses
</button>
@@ -859,6 +863,8 @@ class="space-y-6"
</div>
</div>
{% include "projects/partials/board_automation_modal.html" %}
{% endif %}
</div>
{% endblock %}
+19 -2
View File
@@ -24,8 +24,22 @@
</div>
<div class="flex items-center gap-2">
<a href="{% url 'projects-list' %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-arrow-left mr-1"></i> Back</a>
<a href="{% url 'project-board-project' project.id %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-columns"></i> Project Board</a>
<button type="button" @click="$store.projectDetail.externalModalOpen = true" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-globe"></i> External Dashboard</button>
<a href="{% url 'project-board-project' project.id %}" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-columns"></i> Board</a>
<button @click="window.location.reload()" class="dk-btn dk-btn-ghost text-sm text-gray-700 dark:text-gray-300">
<i class="fas fa-sync"></i> Refresh
</button>
<button @click="$store.projectDetail.externalModalOpen = true" class="dk-btn dk-btn-ghost text-sm"><i class="fas fa-globe"></i> External</button>
{% if is_admin %}
<button @click="$store.projectDetail.permissionsOpen = true" class="dk-btn dk-btn-ghost text-sm text-orange-600">
<i class="fas fa-shield-halved"></i> Permissions
</button>
<button @click="$store.projectDetail.automationOpen = true" class="dk-btn dk-btn-ghost text-sm text-cyan-600">
<i class="fas fa-robot"></i> Automation
</button>
<button @click="$store.projectDetail.manageStatusesOpen = true" class="dk-btn dk-btn-ghost text-sm text-purple-600">
<i class="fas fa-cog"></i> Statuses
</button>
{% endif %}
<a href="{% url 'project-edit' project.id %}" class="dk-btn dk-btn-primary text-sm"><i class="fas fa-pen"></i> Edit</a>
</div>
</div>
@@ -796,6 +810,8 @@
</div>
</div>
{% include "projects/partials/board_automation_modal.html" with selected_project=project %}
{% endblock %}
{% block extra_scripts %}
@@ -811,6 +827,7 @@
viewNoteData: { id: '', title: '', content: '' },
manageStatusesOpen: false,
permissionsOpen: false,
automationOpen: false,
createStatusName: '',
createStatusColor: '#6B7280',
createStatusOrder: '0',
@@ -0,0 +1,159 @@
{% load static %}
<!-- Automation Rules Modal -->
<div x-cloak x-show="automationOpen" class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
@keydown.escape.window="automationOpen = false">
<div class="fixed inset-0 bg-black/50" @click="automationOpen = false"></div>
<div class="relative bg-white dark:bg-gray-800 rounded-xl shadow-2xl w-full max-w-4xl mx-4 max-h-[90vh] overflow-y-auto p-6 md:p-8">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-bold text-gray-900 dark:text-white">Automation Rules</h3>
<button @click="automationOpen = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
<i class="fas fa-times text-xl"></i>
</button>
</div>
<!-- Add Rule Form -->
<form method="POST" action="{% url 'board-automation-rules' selected_project.id %}"
x-data="{ actionRows: [] }"
class="mb-6 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg border border-gray-200 dark:border-gray-600">
{% csrf_token %}
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-4">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Rule Name</label>
<input type="text" name="name" required
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-500 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-cyan-500 focus:border-transparent"
placeholder="e.g. Assign QA on Done">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Trigger Event</label>
<select name="trigger_event"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-500 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-cyan-500 focus:border-transparent">
<option value="status_changed">Status becomes</option>
</select>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Trigger Status</label>
<select name="trigger_status"
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-500 rounded-lg text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-cyan-500 focus:border-transparent">
<option value="">--- Any Status ---</option>
{% for col in columns %}
<option value="{{ col.status.id }}">{{ col.status.name }}</option>
{% endfor %}
</select>
</div>
</div>
<!-- Actions Builder -->
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Actions</label>
<div class="space-y-2">
<template x-if="actionRows.length === 0">
<p class="text-sm text-gray-400 italic">No actions configured. Click "Add Action" to begin.</p>
</template>
<template x-for="(row, idx) in actionRows" :key="idx">
<div class="flex items-center gap-2 p-2 bg-white dark:bg-gray-700 rounded border border-gray-200 dark:border-gray-500">
<select x-model="row.type"
class="flex-1 min-w-[130px] px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="assign_user">Assign user</option>
<option value="change_status">Change status</option>
<option value="notify_in_app">Notify in-app</option>
<option value="send_email">Send email</option>
</select>
<input x-show="row.type === 'assign_user'" x-model="row.user_id" type="number" placeholder="User ID"
class="w-24 px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<select x-show="row.type === 'change_status'" x-model="row.status_id"
class="w-40 px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<option value="">--- Status ---</option>
{% for col in columns %}
<option value="{{ col.status.id }}">{{ col.status.name }}</option>
{% endfor %}
</select>
<div x-show="row.type === 'send_email' || row.type === 'notify_in_app'" class="flex gap-2 flex-1">
<input x-model="row.title" type="text" placeholder="Title"
class="flex-1 px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
<input x-model="row.message" type="text" placeholder="Message"
class="flex-1 px-2 py-1.5 border border-gray-300 dark:border-gray-500 rounded text-sm bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100">
</div>
<button @click="actionRows.splice(idx, 1)" type="button" class="text-red-500 hover:text-red-700 px-1 flex-shrink-0">
<i class="fas fa-times"></i>
</button>
</div>
</template>
</div>
<button @click="actionRows.push({type: 'assign_user', user_id: '', status_id: '', title: '', message: ''})" type="button"
class="mt-2 text-sm text-cyan-600 hover:text-cyan-800 font-medium">
<i class="fas fa-plus"></i> Add Action
</button>
</div>
<input type="hidden" name="actions" x-bind:value="JSON.stringify(actionRows)">
<div class="flex justify-end">
<button type="submit" class="px-4 py-2 bg-cyan-600 text-white text-sm font-medium rounded-lg hover:bg-cyan-700 transition-colors">
<i class="fas fa-plus"></i> Add Rule
</button>
</div>
</form>
<!-- Rules List -->
{% if automation_rules %}
<div class="overflow-x-auto">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-gray-200 dark:border-gray-600">
<th class="text-left py-2 px-3 font-medium text-gray-700 dark:text-gray-300">Name</th>
<th class="text-left py-2 px-3 font-medium text-gray-700 dark:text-gray-300">Trigger</th>
<th class="text-left py-2 px-3 font-medium text-gray-700 dark:text-gray-300">Actions</th>
<th class="text-center py-2 px-3 font-medium text-gray-700 dark:text-gray-300">Active</th>
<th class="text-right py-2 px-3 font-medium text-gray-700 dark:text-gray-300">Delete</th>
</tr>
</thead>
<tbody>
{% for rule in automation_rules %}
<tr class="border-b border-gray-100 dark:border-gray-700">
<td class="py-2 px-3 text-gray-900 dark:text-gray-100 font-medium">{{ rule.name }}</td>
<td class="py-2 px-3 text-gray-600 dark:text-gray-400">
When status becomes
{% if rule.trigger_status %}
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
style="background-color: {{ rule.trigger_status.color }}22; color: {{ rule.trigger_status.color }};">
{{ rule.trigger_status.name }}
</span>
{% else %}
<span class="text-gray-400 italic">any</span>
{% endif %}
</td>
<td class="py-2 px-3">
{% for action in rule.actions %}
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 mr-1 mb-1">
{% if action.type == 'assign_user' %}<i class="fas fa-user-plus"></i> Assign{% elif action.type == 'change_status' %}<i class="fas fa-arrow-right"></i> Change status{% elif action.type == 'notify_in_app' %}<i class="fas fa-bell"></i> Notify{% elif action.type == 'send_email' %}<i class="fas fa-envelope"></i> Email{% else %}{{ action.type }}{% endif %}
</span>
{% empty %}
<span class="text-xs text-gray-400 italic">none</span>
{% endfor %}
</td>
<td class="py-2 px-3 text-center">
<form method="POST" action="{% url 'board-automation-rule-toggle' selected_project.id rule.id %}">
{% csrf_token %}
<button type="submit" class="relative inline-flex h-5 w-9 items-center rounded-full transition-colors cursor-pointer {% if rule.is_active %}bg-emerald-500{% else %}bg-gray-300 dark:bg-gray-600{% endif %}">
<span class="inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform {% if rule.is_active %}translate-x-[18px]{% else %}translate-x-1{% endif %}"></span>
</button>
</form>
</td>
<td class="py-2 px-3 text-right">
<form method="POST" action="{% url 'board-automation-rule-delete' selected_project.id rule.id %}" onsubmit="return confirm('Delete rule &quot;{{ rule.name }}&quot;?')">
{% csrf_token %}
<button type="submit" class="text-red-500 hover:text-red-700 text-sm cursor-pointer">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-sm text-gray-400 italic text-center py-8">No automation rules configured yet. Add one above.</p>
{% endif %}
</div>
</div>