164 lines
6.9 KiB
TypeScript
164 lines
6.9 KiB
TypeScript
import { useState } from 'react'
|
|
import { Card, Button, Input, Select, Modal, Badge } from '../components/ui'
|
|
|
|
interface Rule {
|
|
id: string
|
|
name: string
|
|
enabled: boolean
|
|
trigger: string
|
|
actions: string[]
|
|
runs: number
|
|
}
|
|
|
|
const mockRules: Rule[] = [
|
|
{ id: '1', name: 'Auto-assign critical tickets', enabled: true, trigger: 'When priority is Critical', actions: ['Assign to On-Call', 'Send Slack notification'], runs: 23 },
|
|
{ id: '2', name: 'Close stale tickets', enabled: true, trigger: 'When ticket has no activity for 14 days', actions: ['Add comment', 'Close ticket'], runs: 45 },
|
|
{ id: '3', name: 'Escalate high priority', enabled: false, trigger: 'When high priority ticket is open for 24h', actions: ['Send email to manager'], runs: 12 },
|
|
]
|
|
|
|
export default function Automation() {
|
|
const [rules, setRules] = useState<Rule[]>(mockRules)
|
|
const [showCreate, setShowCreate] = useState(false)
|
|
|
|
const toggleRule = (id: string) => {
|
|
setRules(rules.map(r => r.id === id ? { ...r, enabled: !r.enabled } : r))
|
|
}
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900">Automation</h1>
|
|
<p className="text-gray-500">Automate repetitive tasks with rules</p>
|
|
</div>
|
|
<Button onClick={() => setShowCreate(true)}>
|
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
|
</svg>
|
|
Create Rule
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Stats */}
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
|
<Card>
|
|
<div className="text-center">
|
|
<div className="text-3xl font-bold text-gray-900">{rules.length}</div>
|
|
<div className="text-sm text-gray-500">Total Rules</div>
|
|
</div>
|
|
</Card>
|
|
<Card>
|
|
<div className="text-center">
|
|
<div className="text-3xl font-bold text-green-600">{rules.filter(r => r.enabled).length}</div>
|
|
<div className="text-sm text-gray-500">Active Rules</div>
|
|
</div>
|
|
</Card>
|
|
<Card>
|
|
<div className="text-center">
|
|
<div className="text-3xl font-bold text-blue-600">{rules.reduce((a, r) => a + r.runs, 0)}</div>
|
|
<div className="text-sm text-gray-500">Total Runs</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Rules List */}
|
|
<div className="space-y-4">
|
|
{rules.map(rule => (
|
|
<Card key={rule.id}>
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<button
|
|
onClick={() => toggleRule(rule.id)}
|
|
className={`w-12 h-6 rounded-full transition-colors relative ${rule.enabled ? 'bg-green-500' : 'bg-gray-300'}`}
|
|
>
|
|
<span className={`absolute w-5 h-5 bg-white rounded-full top-0.5 transition-transform ${rule.enabled ? 'translate-x-6' : 'translate-x-0.5'}`} />
|
|
</button>
|
|
<div>
|
|
<div className="flex items-center gap-2">
|
|
<h3 className="font-semibold text-gray-900">{rule.name}</h3>
|
|
<Badge variant={rule.enabled ? 'success' : 'default'}>{rule.enabled ? 'Active' : 'Disabled'}</Badge>
|
|
</div>
|
|
<p className="text-sm text-gray-500 mt-1">{rule.trigger}</p>
|
|
<div className="flex gap-2 mt-2">
|
|
{rule.actions.map((action, i) => (
|
|
<span key={i} className="text-xs bg-gray-100 text-gray-600 px-2 py-1 rounded">{action}</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-4">
|
|
<div className="text-right">
|
|
<div className="text-lg font-semibold text-gray-900">{rule.runs}</div>
|
|
<div className="text-xs text-gray-500">runs</div>
|
|
</div>
|
|
<Button variant="ghost" size="sm">Edit</Button>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
|
|
{/* Create Modal */}
|
|
<Modal open={showCreate} onClose={() => setShowCreate(false)} title="Create Automation Rule" size="lg">
|
|
<div className="space-y-4">
|
|
<Input label="Rule Name" placeholder="My automation rule" />
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Trigger</label>
|
|
<Select
|
|
options={[
|
|
{ value: 'created', label: 'When a ticket is created' },
|
|
{ value: 'updated', label: 'When a ticket is updated' },
|
|
{ value: 'status_changed', label: 'When status changes' },
|
|
{ value: 'priority_changed', label: 'When priority changes' },
|
|
{ value: 'stale', label: 'When ticket becomes stale' },
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Conditions</label>
|
|
<div className="space-y-2">
|
|
<div className="flex gap-2">
|
|
<Select options={[
|
|
{ value: 'priority', label: 'Priority' },
|
|
{ value: 'status', label: 'Status' },
|
|
{ value: 'assignee', label: 'Assignee' },
|
|
{ value: 'project', label: 'Project' },
|
|
]} className="w-40" />
|
|
<Select options={[
|
|
{ value: 'eq', label: 'equals' },
|
|
{ value: 'neq', label: 'not equals' },
|
|
{ value: 'contains', label: 'contains' },
|
|
]} className="w-40" />
|
|
<Input placeholder="Value" className="flex-1" />
|
|
</div>
|
|
</div>
|
|
<Button variant="ghost" size="sm" className="mt-2">+ Add condition</Button>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">Actions</label>
|
|
<div className="space-y-2">
|
|
<Select options={[
|
|
{ value: 'assign', label: 'Assign to user' },
|
|
{ value: 'comment', label: 'Add comment' },
|
|
{ value: 'status', label: 'Change status' },
|
|
{ value: 'priority', label: 'Change priority' },
|
|
{ value: 'notify', label: 'Send notification' },
|
|
{ value: 'webhook', label: 'Call webhook' },
|
|
]} />
|
|
</div>
|
|
<Button variant="ghost" size="sm" className="mt-2">+ Add action</Button>
|
|
</div>
|
|
|
|
<div className="flex gap-3 pt-4 border-t">
|
|
<Button variant="secondary" className="flex-1" onClick={() => setShowCreate(false)}>Cancel</Button>
|
|
<Button className="flex-1">Create Rule</Button>
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|